Skip to content

refactor(ai): add debate-aware context and focused feature handlers - #75

Merged
Programcode merged 9 commits into
mainfrom
refactor/debate-aware-ai
Sep 28, 2026
Merged

Programcode merged 9 commits into
mainfrom
refactor/debate-aware-ai

Conversation

@Programcode

@Programcode Programcode commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Implements PR 2 from .hermes/plans/2026-09-25_074255-debate-ai-quality-modules.md: every AI feature now uses the Current Debate, the seven feature prompts are sharper, and the 89 KB AI entrypoint is split into focused modules. The deployed ai and ai-stream endpoints keep their names and request shape; the new field is additive.

What changes for students

A short line sits directly above the main button in Generate, Attack, Brainstorm, Factoids, Ask, Crossfire, and Sparring, for example "Using 2 sources and 1 saved result: Case (In favor)". Review opens a sheet that lists the debate's Library sources and saved results with checkboxes for the next request (for the whole round in Crossfire and Sparring, where Review appears only before the round starts), pin buttons that keep a result in every request for that debate, and notes for anything over the limit, too large, or empty. After a request, the sheet reports what actually reached the model. One-off mode says that no debate material will be used or saved.

Screenshots come from the local full-stack Playwright run (real local Supabase, fake model provider). They're stored on the evidence-only branch evidence/debate-aware-ai, so the links work for signed-in collaborators.

Generate, desktop and mobile: the context line above the button.

Desktop: Generate context line
Mobile: Generate context line

Brainstorm Review sheet, after unchecking one source and pinning the saved case. On desktop, the full-page capture draws the sticky header partway down behind the fixed sheet; that is an artifact of the capture method, not of the layout.

Desktop: Review sheet
Mobile: Review sheet

How context works

  • The browser sends only IDs: debateContext: { debateId, side, sourceIds, outputs: [{ kind, id }] }. The default selection is the debate's active sources, then pinned results, then up to three of the newest results of the types that feature uses, for the requested side. The rules live in _shared/debate-context.ts, which both the browser and the server import.
  • _shared/debate-context-loader.ts loads the motion, settings, instructions, sources, and result digests with the service role. Every query filters by the caller's user ID and the one debate. A debate the caller doesn't own returns 404 before any quota, routing, or provider work. Forged or cross-debate IDs are never loaded; they're counted as unavailable.
  • The prompt puts this material in delimited blocks. Stored text cannot close them, and a closing "student material" section says it can't override rules, schemas, citations, or safety.
  • Per-feature limits:
    • Sparring stays transcript-only (no sources or saved results), and its persisted scorecard sees only the motion, side, and settings, not free-form instructions.
    • Crossfire uses saved results but not free-form instructions, which keeps its existing guarantee that custom prompts never apply to it.
  • Whole saved results are dropped rather than cut when their 8,000-character budget runs out. meta.debateContext reports what reached the model.
  • The per-user customSystemPrompt is retired. The browser no longer sends it, and the server ignores it from older clients. Operator prompts in global_prompts are unchanged.
  • New table debate_context_pins (additive migration with RLS, a composite key to the owner's debate, and explicit grants).

Prompts

Each feature now has its own module in _shared/prompts/, with shared rules in common.ts:

  • Generate. Speech-ready claim, warrant, evidence, and impact with explicit weighing, plus a consistent team line.
  • Attack. Each weakness quotes its exact target, names the broken link, and gives a response the student can say word for word.
  • Brainstorm. Directions that differ in mechanism, stakeholder, or value, and skip ideas already saved.
  • Factoids. Exact figures from the source, and limitations that are never empty.
  • Ask. Answers the mode directly and uses only what it received.
  • Crossfire. Presses dodges, and counts a concession only when the speaker's own words accept the point.
  • Sparring. Stays in role, and gives feedback tied to specific speeches with nothing about voice or confidence.

Hard-coded year ranges are gone. prompt-examples.ts holds two representative requests per feature (one per side where the feature has sides). A Deno test checks each against its feature's rules and the fake provider's routing lines.

The runner (scripts/prompt-examples.ts) sent the 14 examples to OpenRouter with openai/gpt-5.6-luna, once with main's prompts and once with this branch's. These were live model calls, not fixtures: 28 calls, all HTTP 200, about $0.047 in total. The system prompt grew by 2,100 to 3,000 characters per example (Generate: about 10,600 to 13,600).

Two representative differences:

  • Attack, pro case. Before, the top weakness was "the claim that studies show homework is bad is unsupported". After, it names the exact jump in the case (from family fights to banning all homework) and the proportionality gap an opponent would use.
  • Crossfire debrief, con. Before, it gave a generic "controlled questions" entry spanning two turns. After, it separates the question that worked (T1) from the one that let the answer reject the framing (T3). It also records the concession in the speaker's own terms: variation in support was accepted, and unfairness was denied.

These calls ran on the prompt text at 55e1356. The later commits don't change prompt text.

Code layout

ai/index.ts shrank from 1,930 to about 800 lines and remains the entrypoint and dispatcher. One handler per feature lives in ai/features/, with provider.ts, request.ts, runtime.ts, and text.ts beside it. _shared/prompts.ts and ai/prompt-builder.ts stay as re-export facades. The split landed first as its own commit (11c736c), with every system and user prompt byte-identical to main.

Commit 49e67ab fixes an unrelated test time bomb: the workspace-invite email fixtures hard-coded an expiry of 2026-09-27 12:00 UTC, so four Edge tests started failing on main today.

Deploy order (not done here)

The client is backward compatible: it still sends the legacy sourceIds, and pins fail soft when the table is missing. So Vercel can go first. To turn the feature on:

  1. Apply 20260927170000_debate_context_pins.sql (additive).
  2. Deploy the ai and ai-stream Edge Functions.
  3. Check one Generate request in production with debate material and one in one-off mode.

Until step 2, the new client works against the old functions exactly as today.

Verification

All results below are local, at 4a6ba4a. GitHub Actions don't start on this repository because of the account's billing limit, so no hosted CI result exists for this head. These local runs are the equivalents of the hosted jobs.

  • ./.superset/db-gate.sh (Docker Supabase, pgTAP, quota and concurrency scripts, full-stack Playwright): exit 0. 345 pgTAP tests passed. Playwright: 55 passed, 1 skipped (the mobile variant of a desktop-only workspace-collaboration test).
  • npm run test:edge: 270 passed, 0 failed.
  • npm run verify: exit 0. Docs check, lint, and typecheck are clean. Vitest: 1,093 tests in 109 files passed, 90.3% statement coverage. Collaboration: 5 files passed. Edge: 270 passed. Build and service-worker tests passed. Chromium E2E, desktop and mobile, against a fresh Vite server: 225 passed, 6 skipped, 1 flaky. The flaky test is e2e/home-navigation.spec.ts:89. It asserts with a 100 ms timeout that the onboarding tooltip is gone, and it passed on retry. This branch doesn't touch that spec or onboarding.
  • Not run: WebKit smoke, staging E2E (no staging credentials in this environment), and production checks. Nothing was deployed.

Fixes from the pre-PR review

  • Crossfire and Sparring applied Review choices to the first turn only. The client cleared choices after every request, so the second turn quietly restored excluded sources. Round turns now keep them; the debrief or judging clears them, as do restarting and abandoning. crossfire-client.test.ts sends two turns and a debrief. It fails without the fix.
  • A near-maximum Crossfire round failed with a 500. The saved results pushed the prompt past its 100,000-character budget. Now the handler drops the results, and meta.debateContext lists them in outputsOmittedForSize, not outputsUsed. The Edge test in ai/debate-context.test.ts reproduced the 500 before the fix.
  • A pin load or a failed pin save could bring back an unpinned result (found by CodeRabbit's security review). Pin changes now bump a per-debate counter that a stale load respects, and a failed save undoes only its own change. Two tests reproduce the races and fail without the fix.
  • A debate deleted on another device returned 404 with no recovery. Both the ai and ai-stream clients now reload the debate list on that 404, with a unit test that fails without the change.
  • Full-stack spec expectation. It expected one saved result on the second Brainstorm. The Brainstorm that the first request just saved correctly joins the defaults under the recency rule, so the spec now expects it, and it checks that the first request's usage (1 source, 1 result) is what the sheet reports.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • AI requests can use the current debate’s instructions, active sources, and relevant saved results.
    • Review and adjust the material included in each request, pin saved results for future selection, and see what was used, omitted, or shortened.
    • Debate context is available across debate-preparation and practice tools. One-off and workspace requests are handled separately.
  • Changes
    • Custom user system prompts are no longer supported.

Programcode and others added 8 commits September 27, 2026 16:08
The email fixtures hard-coded an expiry of 2026-09-27 12:00 UTC, so four
tests began failing once that moment passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move code without changing behaviour so later prompt and context changes
are easy to review.

- ai/index.ts stays the HTTP entrypoint and dispatcher. Auth, validation,
  entitlements, routing, source ranking, and usage accounting run there
  once, then each feature has its own handler in ai/features/.
- Environment and service-role clients live in ai/runtime.ts, provider
  calls in ai/provider.ts, and input hygiene in ai/request.ts.
- Each feature's task prompt and user-prompt builder moved to
  _shared/prompts/<feature>.ts; shared rules to _shared/prompts/common.ts.
  _shared/prompts.ts and ai/prompt-builder.ts remain as re-export facades.
- The security source-scan test now reads every non-test ai source.

Every system and user prompt is byte-identical to the previous build.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every ai and ai-stream request can now carry a debateContext of IDs and
preferences. The server loads the motion, side, settings, instructions,
Library sources, and saved results for that debate, filtering every query
by the caller and the one debate, so forged or cross-debate IDs never load.
Ownership is checked before quota, routing, or provider work.

- The student's material renders in delimited blocks; stored text cannot
  close them, and the untrusted-material rules close the system prompt.
- Sparring stays transcript-only. Its scorecard sees no instructions or
  saved results. Crossfire uses saved results but not free-form
  instructions.
- Whole saved results drop out when the budget is spent. The response's
  meta.debateContext reports what reached the model and what did not.
- The per-user custom system prompt layer is retired; an old client's
  customSystemPrompt field is ignored. Admin prompts are unchanged.
- Legacy sourceIds still work for clients that do not send debateContext.
- A debate_context_pins table stores results the student pins per debate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…eature

A compact line directly above the primary button in Generate, Attack,
Brainstorm, Factoids, Ask, Crossfire, and Sparring says which sources and
saved results the next request will use. Review opens a sheet with
checkboxes for that request, pins that persist per debate, and honest
notes for material that is over the limit, too large, or empty. One-off
mode says no debate material will be used or saved.

The browser computes the default selection with the same shared rules as
the server and sends only IDs. Review choices reset after one successful
request, and the last response's usage report shows what reached the
model. Sparring requests now use the round's own debate rather than
whichever debate is current.

The browser no longer sends the retired custom system prompt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Generate: speech-ready claim, warrant, evidence, and impact with explicit
  weighing, and a consistent team line across speakers.
- Attack: each weakness quotes its exact target, names the broken link,
  and gives a response the student can say word for word.
- Brainstorm: ideas rest on different mechanisms, stakeholders, or values,
  and skip directions already saved in the debate.
- Factoids: exact figures from the source and a limitations field that
  always labels the uncertainty.
- Ask: answers the mode directly and uses only the material it received.
- Crossfire: follows up on dodges and counts a concession only when the
  speaker's own words accept the point.
- Sparring: stays in role during the round, and scores with feedback tied
  to specific speeches and nothing about voice or confidence.
- Year ranges and a reference to a file the model cannot see are gone.

prompt-examples.ts holds two representative requests per feature, one
per side where the feature has sides. A Deno test checks them against
each feature's rules, and scripts/prompt-examples.ts compares revisions
on one real model. A full-stack journey covers the Review control, and
the architecture, backend, and testing docs describe the new layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Default privileges also gave anon access and UPDATE. A pin is only added
or removed, so revoke everything and grant SELECT, INSERT, and DELETE to
authenticated users.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…leted debates

- Crossfire and Sparring show Review once, before the round. Their turns
  now keep those choices; the debrief or judging clears them, as does
  restarting or abandoning the round. Before, the second turn silently
  restored excluded sources and results.
- A Crossfire round near its 100,000-character prompt budget no longer
  fails. It drops the saved results, and meta.debateContext lists them as
  omitted for size instead of used.
- A debate request that returns 404 (deleted on another device) reloads
  the debate list.
- The full-stack spec now expects the result a request just saved to join
  the next request's defaults, as the recency rule intends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 28, 2026 00:08
@vercel

vercel Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Sep 28, 2026 3:20pm UTC

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Programcode has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 6ace59ed-0124-4290-a00c-8c6543715fb4

📥 Commits

Reviewing files that changed from the base of the PR and between 0bda733 and 4a6ba4a.

📒 Files selected for processing (73)
  • docs/architecture.md
  • docs/testing.md
  • e2e/full-stack/debate-context.spec.ts
  • scripts/prompt-examples.ts
  • src/components/features/ask/AskChat.tsx
  • src/components/features/attack/AttackForm.tsx
  • src/components/features/brainstorm/BrainstormForm.tsx
  • src/components/features/debate/DebateContextControl.tsx
  • src/components/features/factoids/FactoidsForm.tsx
  • src/components/features/generate/GenerateForm.tsx
  • src/components/features/sparring/SparringSetup.tsx
  • src/lib/ai/client.ts
  • src/lib/ai/streaming.ts
  • src/lib/debate/context-state.ts
  • src/lib/debate/context.ts
  • src/lib/stores/debate-context-store.ts
  • src/lib/stores/session-store.ts
  • src/lib/stores/sparring-store.ts
  • src/lib/supabase/AuthProvider.tsx
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/hu.json
  • src/pages/AskPage.tsx
  • src/pages/AttackPage.tsx
  • src/pages/CrossfirePage.tsx
  • src/pages/FactoidsPage.tsx
  • src/pages/SparringPage.tsx
  • src/test/ai/client-branches.test.ts
  • src/test/ai/client.test.ts
  • src/test/ai/crossfire-client.test.ts
  • src/test/ai/request-dispatch.test.ts
  • src/test/debate/debate-context.test.tsx
  • src/test/security-hardening.test.ts
  • supabase/README.md
  • supabase/functions/_shared/debate-context-loader.ts
  • supabase/functions/_shared/debate-context.test.ts
  • supabase/functions/_shared/debate-context.ts
  • supabase/functions/_shared/prompts.test.ts
  • supabase/functions/_shared/prompts.ts
  • supabase/functions/_shared/prompts/ask.ts
  • supabase/functions/_shared/prompts/attack.ts
  • supabase/functions/_shared/prompts/brainstorm.ts
  • supabase/functions/_shared/prompts/common.ts
  • supabase/functions/_shared/prompts/context.ts
  • supabase/functions/_shared/prompts/crossfire.ts
  • supabase/functions/_shared/prompts/factoids.ts
  • supabase/functions/_shared/prompts/generate.ts
  • supabase/functions/_shared/prompts/prompt-examples.test.ts
  • supabase/functions/_shared/prompts/prompt-examples.ts
  • supabase/functions/_shared/prompts/sparring.ts
  • supabase/functions/_shared/prompts/system.ts
  • supabase/functions/_shared/source-bounds.ts
  • supabase/functions/_shared/testing/fake-rest.ts
  • supabase/functions/ai-stream/index.ts
  • supabase/functions/ai/debate-context.test.ts
  • supabase/functions/ai/features/ask.ts
  • supabase/functions/ai/features/attack.ts
  • supabase/functions/ai/features/brainstorm.ts
  • supabase/functions/ai/features/crossfire-input.ts
  • supabase/functions/ai/features/crossfire.ts
  • supabase/functions/ai/features/factoids.ts
  • supabase/functions/ai/features/generate.ts
  • supabase/functions/ai/features/sparring.ts
  • supabase/functions/ai/features/types.ts
  • supabase/functions/ai/index.ts
  • supabase/functions/ai/prompt-builder.ts
  • supabase/functions/ai/provider.ts
  • supabase/functions/ai/request.ts
  • supabase/functions/ai/runtime.ts
  • supabase/functions/ai/text.ts
  • supabase/functions/workspace-invites/index_test.ts
  • supabase/migrations/20260927170000_debate_context_pins.sql
  • supabase/tests/debate_context_pins_test.sql

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

AI requests now send Current Debate context IDs for server-side loading and feature-specific prompt use. The change adds context review and pinning controls, updates AI request handling and prompt modules, and adds tests, documentation, and prompt tools.

Changes

Current Debate context and AI requests

Layer / File(s) Summary
Context selection and planning
src/lib/debate/context.ts, src/lib/debate/context-state.ts, supabase/functions/_shared/debate-context.ts, supabase/functions/_shared/source-bounds.ts
Shared planning selects eligible debate sources and saved results, applies feature and side policies, and records inclusion and size status.
Review controls and saved pins
src/components/features/debate/DebateContextControl.tsx, src/components/features/{ask,attack,brainstorm,factoids,generate,sparring}/*, src/pages/{AskPage,AttackPage,CrossfirePage,FactoidsPage,SparringPage}.tsx, src/lib/stores/debate-context-store.ts, supabase/migrations/20260927170000_debate_context_pins.sql, supabase/tests/debate_context_pins_test.sql, src/locales/*.json
Forms and Ask display review controls for debate context. Users can adjust source and result choices and pin results. The store tracks choices and usage. The migration adds debate-scoped pins with row-level access policies.
Client requests and server context loading
src/lib/ai/client.ts, src/lib/ai/streaming.ts, supabase/functions/ai/index.ts, supabase/functions/ai-stream/index.ts, supabase/functions/_shared/debate-context-loader.ts, e2e/full-stack/debate-context.spec.ts, src/test/debate/debate-context.test.tsx
AI clients send context IDs and record returned usage. Endpoints load material scoped to the authenticated user and requested debate, then return context metadata. Tests cover request selection, ownership, Review choices, and one-off behavior.
Prompt composition and feature handlers
supabase/functions/_shared/prompts/*, supabase/functions/_shared/prompts.ts, supabase/functions/ai/features/*, supabase/functions/ai/{index,prompt-builder,provider,request,runtime,text}.ts, scripts/prompt-examples.ts, supabase/functions/_shared/prompts/prompt-examples*
Prompt definitions and builders are organized into shared and feature-specific modules. AI feature handlers and supporting request, runtime, and provider utilities are split into dedicated modules. Prompt examples, tests, and the comparison script are added. Documentation describes the dispatcher, context behavior, and test commands.

Workspace invite test fixtures

Layer / File(s) Summary
Dynamic pending invite expirations
supabase/functions/workspace-invites/index_test.ts
Pending invite test fixtures use expirations computed as 24 hours after the test run time.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AIClient
  participant AIEndpoint
  participant ContextLoader
  participant PromptBuilder
  participant AIProvider
  AIClient->>AIEndpoint: Send debateContext IDs and feature input
  AIEndpoint->>ContextLoader: Load caller-scoped debate material
  ContextLoader-->>AIEndpoint: Return validated sources and output digests
  AIEndpoint->>PromptBuilder: Build feature prompt with debate context
  PromptBuilder-->>AIEndpoint: Return system and user prompts
  AIEndpoint->>AIProvider: Submit prompt
  AIProvider-->>AIEndpoint: Return generated output
  AIEndpoint-->>AIClient: Return output and context usage metadata
Loading

Security Architecture Review

Security architecture risk: 🔵 Low · up to 3c96d

A timing issue can cause a result that was just unpinned to be selected for a later AI request. The affected material remains within the signed-in user’s debate; the review did not establish a cross-user access path.

Retained concerns

  • Low · security · inferred: An in-flight pin load can restore a result after the user unpins it. Because later requests are planned from local pin state, the result can again be selected for transmission to the AI provider unless it is explicitly excluded for that request.
Security review details

Security Blast Radius

  • inferred — The identified pin race can affect selection of saved results within the user’s debate for a subsequent provider request. The inspected server path does not show a route from a pin to another user’s material.

Security Findings and Attack Paths

  • inferred — If a pin read obtains the old set, an unpin completes, and that read then updates local state, the removed pin reappears. The planner can include its saved result in a later request without a new pin action.

Trust Boundaries and Controls

  • observed — The server authenticates before loading selected material and applies caller-and-debate filters. An explicit output exclusion in the browser planner overrides automatic selection, including a pin.

Resilience and Maintainability Implications

  • inferred — The same unversioned pin state is written by background loading and optimistic mutations; a failed concurrent mutation can also restore a snapshot that predates another successful choice.

Hardening Proposals

  • proposed — Serialize or version pin reads and mutations per debate, and prevent a stale response or rollback from replacing a newer choice before the next request is planned.

Warning

Tools execution failed with the following error:

Failed to run tools: Ping-pong health check failed


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai Thanks. On the retained low-severity concern (debate material loads before the request-limit check): we're keeping this ordering on purpose.

  • Only a signed-in caller can reach the loader. Every query is filtered by that caller's user ID and one debate, so no path reaches another user's material.
  • Each request's reads are bounded: at most 6 saved results, a capped list of source IDs, and bounded chat-message reads. The loader is read-only.
  • Checking ownership first means a forged or foreign debate returns 404 without charging the student's daily quota. That was the design requirement in the plan.
  • Provider work, the expensive part, stays behind the entitlement and quota checks.

A dedicated throttle in front of the loader would add another admission path for a small, bounded read. It's a reasonable follow-up if abuse ever shows up in ai_requests logs, but it is outside this PR's scope.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown

The author of this PR is on the CodeRabbit Free Plan. In order to use the Chat feature, please upgrade the PR author to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

… late

A pin load that started before an unpin could bring the unpinned result
back, and a failed save restored the whole list from before it, undoing
other pin changes. Either way a result the student had unpinned could
reach a later request. Pin changes now bump a per-debate counter that a
stale load respects, and a failed save undoes only its own change.

Found by CodeRabbit's security review on #75.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Programcode

Copy link
Copy Markdown
Owner Author

Fixed CodeRabbit's pin-race finding in 4a6ba4a. A pin change now bumps a per-debate counter, and a load that started before the change no longer overwrites it. A failed save undoes only its own change instead of restoring an older list. Two new tests reproduce both races and fail without the fix. npm run verify at 4a6ba4a exited 0 (1,093 unit tests; E2E 225 passed, 6 skipped, 1 flaky onboarding-timing test that passed on retry). The DB gate is rerunning now.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@Programcode

Copy link
Copy Markdown
Owner Author

The retained pin-race concern in the security section (still marked "up to 3c96d") is addressed in 4a6ba4a, following the proposed hardening. loadPins records a per-debate change counter before reading and skips the overwrite if a pin changed during the read. A failed togglePin now undoes only its own ref instead of restoring the older snapshot. src/test/debate/debate-context.test.tsx reproduces both orderings (unpin during an in-flight load, and a failed pin while another unpin succeeds), and both tests failed before the fix.

@Programcode
Programcode merged commit f0f2f75 into main Sep 28, 2026
5 of 9 checks passed
@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

5a53509 fixes two pin-store issues found in an internal review:

  • A pin made before the first pin load returned used to discard that load. Loads now replay local changes on top of the saved rows.
  • A pin on a deleted result counted toward the limit forever. The limit now counts only pins whose result still exists, pin writes for a debate run in order, and pin buttons wait until saved pins have loaded.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Pull request is closed.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 32 minutes.

This branch was successfully deployed

1 active deployment
Preview — 4a6ba4a8 Deployed Sep 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants