Skip to content

feat(ai): PR3a — FE tri-state ui.aiMode migration + define-once SampleBadge - #293

Merged
prpande merged 28 commits into
V2from
feat/v2-ai-pr3-fe-mode-migration
Jun 8, 2026
Merged

feat(ai): PR3a — FE tri-state ui.aiMode migration + define-once SampleBadge#293
prpande merged 28 commits into
V2from
feat/v2-ai-pr3-fe-mode-migration

Conversation

@prpande

@prpande prpande commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Overview

PR3a migrates the PRism frontend off the legacy boolean ui.aiPreview onto PR2's tri-state ui.aiMode (off | preview | live), ships an Off | Preview mode selector (Live is plumbed in the type but intentionally not rendered in P0), and adds a define-once SampleBadge marker to every Preview/sample AI surface. Frontend + docs only — zero backend change (consumes the PR2 wire contract that already accepts ui.ai.mode and emits aiMode).

Implements docs/specs/2026-06-07-v2-ai-p0-pr3a-fe-mode-migration-design.md per docs/plans/2026-06-07-v2-ai-p0-pr3a-fe-mode-migration.md.

Changes

  • ui.aiMode is the FE source of truth. AppearancePane Switch → SegmentedControl (Off/Preview), writing set('ui.ai.mode', mode). A live config displays as Preview with no POST on render.
  • Capabilities stay derived locally (aiMode==='preview' ? AllOn : AllOff) — no /api/capabilities fetch; preserves the reactive architecture and its no-fetch regression test.
  • useAiGate two-factor gate (capability) && aiMode!=='off'; new useIsSampleMode() predicate (true only in Preview).
  • SampleBadge renders only in Preview; replaces the 3 scattered hardcoded "AI preview — sample content…" strings and adds marking to 5 previously-unmarked surfaces (hunk annotations, stale-draft suggestions, activity rail, inbox enrichment, file-focus).
  • Legacy aiPreview preference field fully removed (type, PreferenceKey/readKey/writeKey arms, all mocks, both test trees). The component prop named aiPreview on FileTree/PrDescription is retained (carries the useAiGate value).
  • e2e specs migrated to ui.ai.mode + radio locators + cross-surface badge assertions (with explicit false-green guards).
  • .ai/docs/architectural-invariants.md updated: binary-gate → tri-state AiMode {Off,Preview,Live} + a truthful-by-default SampleBadge invariant.
  • Also migrated the legacy frontend/__tests__/ mirror suite onto aiMode (a parallel test tree the original plan missed), and merged V2 (112 commits) into the branch — one orthogonal conflict in InboxPage.tsx (V2's SpinnerInboxSkeleton loading refactor vs the SampleBadge region marker) resolved.

⚠️ Security Impact — egress-consent dependency

PR3a ships no server-side egress-consent guard. PR3b's per-provider egress-consent gate MUST land before any P1 work registers a real Live AI seam — otherwise a hand-edited ui.ai.mode=live config could egress PR content to a provider with no user consent. In P0 this is latent (Live resolves to all-nine-flags-false; no real seams exist), but it is a hard prerequisite for P1. Filing the dedicated tracking issue is deferred to PR3b planning (maintainer decision); this callout is the interim record.

No other security impact — no auth/crypto/endpoint/input-validation changes; backend untouched.

Testing

  • Unit (Vitest): 1495 passed / 0 failed on the merged tree; npm run build + npm run lint clean.
  • e2e (Playwright): specs migrated; the run + linux visual-baseline regen happen via CI. The Switch→SegmentedControl swap and the SampleBadge additions repixel several visual specs, and CI compares the linux baselines (local --update-snapshots only writes win32), so the changed visual specs are expected to fail on the first CI run and be regenerated from the e2e-results artifact.
  • Final adversarial review over the full diff vs V2: SHIP (all priorities clean).

Related Work

prpande added 21 commits June 7, 2026 18:15
v2 AI P0 PR3a design: migrate FE aiPreview->tri-state ui.aiMode, an Off|Preview segmented selector (Live deferred to PR3b), a define-once SampleBadge sample-data treatment across all Preview surfaces, and the architectural-invariants tri-state update. Backend untouched (consumes the PR2 wire contract).

Revised after two ce-doc-review passes (7 personas each): pass-1 found the false readKey/writeKey claim, the inbox/Activity-rail truthfulness hole, replace-not-duplicate the existing sample labels, the SegmentedControl type cast, and the test-fixture migration; pass-2 found the under-scoped e2e migration (inbox/ai-gating-sweep specs + e2e not type-checked) and the InboxPage vs InboxRow badge placement.
13-task TDD plan from the approved PR3a spec: add ui.aiMode, migrate all consumers (context/hooks/selector/SampleBadge across all Preview surfaces), then remove aiPreview last so every commit compiles; full e2e migration + cross-surface badge coverage; invariant-doc update. Code blocks built from verbatim current source.
Plan pass-1: fixed AiSummaryCard test (non-null summary), hunk-test import depth, clarity on writeKey/useAiGate-imports/full-file-replacement; rejected 3 false-positive import-path P0s (verified correct vs sibling imports). Plan pass-2: visual baselines use the CI linux loop (not local win32 --update-snapshots); added the missing PreSubmitValidatorCard unit test (sweep never opens SubmitDialog); split the e2e false-green guard across nav; named the 3 mocks Task 10 must clean; fixed ai-gating-sweep return-type annotation + line refs; reordered Task 7 to test-first; expanded the e2e capability-mock migration. Spec: realigned the egress-consent dependency to the deferred-tracking-issue decision (PR-body callout interim, issue at PR3b planning).
…ontext

The aimode test only exercised the happy path, so the new writeKey('ui.ai.mode')
arm (reached only on POST-failure rollback) and readKey('ui.ai.mode') (used to
capture priorValue) were never functionally verified — both could be wrong and
the test would stay green. Mirrors the per-key rollback convention already
established in __tests__/hooks.test.tsx for 'theme' and 'inbox.sections.*'.

Adds a rollback-path case: reject the POST, assert the mode reverts to its prior
value (not silently left as 'preview'), an error toast fires, and — the
discriminating assertion — inbox.sections is NOT corrupted with a spurious ''
key. That corruption is exactly what the generic inbox.sections fallthrough
would produce if the dedicated ui.ai.mode writeKey arm were missing; verified the
test fails (received ['']) when the arm is removed and passes ([]) when present.
@
style(ai): remove dead chip CSS orphaned by SampleBadge swap

The `.aiSummaryChip` (AiSummaryCard.module.css) and
`.ai-validator-card__chip` (tokens.css) rules were only consumed by the
`muted` chip wrapper divs removed when the hardcoded sample strings were
replaced with SampleBadge. Grep confirms no remaining consumers in
frontend/, so drop the orphaned rules alongside the markup they styled.
@
Task 7 review fixes:

- Add StaleDraftRow.sample.test.tsx asserting the SampleBadge renders in
  the aiSuggestion branch in preview and is absent in off, matching the
  per-surface convention established by AiHunkAnnotation.sample.test.tsx
  and sampleSurfaces.test.tsx. The draft-suggestion half of this commit
  series previously shipped untested.

- Replace the AiHunkAnnotation fixture's invalid tone 'suggestion' (not
  an AnnotationTone) and the `as unknown as HunkAnnotation` double-cast
  with a complete, real-tone ('calm') HunkAnnotation literal so the
  fixture reflects the wire contract (path, hunkIndex, body, tone).
…truth)

Drops `aiPreview: boolean` from `UiPreferences`, removes the `'aiPreview'`
union member and all `readKey`/`writeKey` arms from `PreferencesContext`,
and scrubs every typed preference-literal mock that still carried the field.
`ui.aiMode` is now the sole source of truth for AI gating in the FE.
…migration

# Conflicts:
#	frontend/src/pages/InboxPage.tsx
Copilot AI review requested due to automatic review settings June 8, 2026 09:32

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.

Pull request overview

This PR migrates the frontend’s AI preference model from the legacy boolean ui.aiPreview to the backend’s tri-state ui.aiMode (off | preview | live), updates the Settings UI to an Off/Preview selector, and standardizes “sample content” labeling across all Preview AI surfaces via a single SampleBadge component.

Changes:

  • Replace the Appearance “AI preview” switch with an Off | Preview segmented selector that writes set('ui.ai.mode', ...), while treating a live config as “Preview” in the UI without posting on render.
  • Update AI gating/capability derivation to use ui.aiMode and add useIsSampleMode(); remove the legacy aiPreview preference field from FE types/mocks/tests.
  • Introduce SampleBadge and wire it into all Preview/sample AI surfaces; migrate unit + e2e tests and update architecture/spec docs accordingly.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/src/styles/tokens.css Removes now-unused pre-submit validator preview chip styling.
frontend/src/pages/InboxPage.tsx Adds region-level SampleBadge for inbox enrichment surface.
frontend/src/hooks/useCapabilities.ts Derives capabilities from ui.aiMode === 'preview'.
frontend/src/hooks/useAiGate.ts Gates on aiMode !== 'off' and adds useIsSampleMode().
frontend/src/hooks/useAiGate.unit.test.tsx New unit coverage for two-factor gate + sample-mode predicate.
frontend/src/hooks/useAiGate.reactivity.test.tsx Migrates reactivity regression test to ui.aiMode.
frontend/src/contexts/PreferencesContext.tsx Migrates preference keying to ui.ai.mode and drops aiPreview.
frontend/src/contexts/PreferencesContext.aimode.test.tsx New tests for ui.ai.mode dotted-key POST + rollback behavior.
frontend/src/components/Settings/panes/AppearancePane.tsx Switch → SegmentedControl (Off/Preview) writing ui.ai.mode.
frontend/src/components/Settings/panes/AppearancePane.test.tsx Updates settings tests to expect the new radiogroup-based control.
frontend/src/components/PrDetail/SubmitDialog/PreSubmitValidatorCard.tsx Replaces legacy preview copy with SampleBadge.
frontend/src/components/PrDetail/Reconciliation/StaleDraftRow.tsx Adds SampleBadge to stale-draft AI suggestion surface.
frontend/src/components/PrDetail/Reconciliation/StaleDraftRow.sample.test.tsx New test asserting badge presence/absence by mode.
frontend/src/components/PrDetail/OverviewTab/OverviewTab.tsx Sources PrDescription.aiPreview from useAiGate('summary').
frontend/src/components/PrDetail/OverviewTab/AiSummaryCard.tsx Replaces legacy preview copy with SampleBadge.
frontend/src/components/PrDetail/OverviewTab/AiSummaryCard.module.css Removes now-unused “chip” styling.
frontend/src/components/PrDetail/FilesTab/FileTree.tsx Adds region SampleBadge to file-tree focus region header.
frontend/src/components/PrDetail/FilesTab/DiffPane/AiHunkAnnotation.tsx Adds inline solid SampleBadge in hunk annotation meta row.
frontend/src/components/PrDetail/FilesTab/DiffPane/AiHunkAnnotation.sample.test.tsx New test asserting badge presence/absence by mode.
frontend/src/components/Ai/SampleBadge.tsx New define-once Preview-only “Sample” marker component.
frontend/src/components/Ai/SampleBadge.module.css Styling for SampleBadge (inline/region/solid variants).
frontend/src/components/Ai/SampleBadge.test.tsx Unit tests for SampleBadge render conditions and variants.
frontend/src/components/Ai/AiComposerAssistant.tsx Replaces legacy preview copy with SampleBadge + remaining text.
frontend/src/components/Ai/sampleSurfaces.test.tsx New test asserting SampleBadge replaces legacy strings on key cards.
frontend/src/components/ActivityRail/ActivityRail.tsx Adds region SampleBadge to Activity rail header.
frontend/src/components/ActivityRail/ActivityRail.sample.test.tsx New test asserting ActivityRail badge presence/absence by mode.
frontend/src/api/types.ts Adds AiMode and replaces ui.aiPreview with ui.aiMode in UI prefs.
frontend/e2e/settings-modal-visual.spec.ts Migrates settings mocks to accept ui.ai.mode dotted-path POST.
frontend/e2e/settings-flow.spec.ts Migrates settings flow to ui.aiMode + ui.ai.mode POST handling.
frontend/e2e/inbox.spec.ts Migrates inbox AI toggle path/handlers to ui.ai.mode + aiMode coupling.
frontend/e2e/helpers/replace-mocks.ts Updates default preferences fixture to ui.aiMode.
frontend/e2e/fixtures/preferences.ts Updates preferences fixture to ui.aiMode.
frontend/e2e/ai-gating-sweep.spec.ts Migrates sweep to radio selector + asserts SampleBadge cross-surface.
frontend/e2e/a11y-audit.spec.ts Updates default preferences to ui.aiMode.
frontend/tests/useAiGate.test.tsx Updates legacy mirror tests to ui.aiMode.
frontend/tests/PrHeader.test.tsx Updates Ask AI gating test setup to aiMode: 'preview'.
frontend/tests/PreferencesContext.test.tsx Updates mock preferences shape to ui.aiMode.
frontend/tests/OverviewTab.test.tsx Updates overview tests for ui.aiMode and SampleBadge expectations.
frontend/tests/InboxPage.test.tsx Updates inbox mocks to ui.aiMode and stubs useIsSampleMode.
frontend/tests/hooks.test.tsx Updates hooks tests to expect ui.aiMode in preferences.
frontend/tests/header.test.tsx Updates header tests’ preferences fixture to ui.aiMode.
frontend/tests/appearance-sync.test.tsx Updates appearance sync test fixtures to ui.aiMode.
frontend/tests/app.test.tsx Updates app test preference mocks to ui.aiMode.
frontend/tests/AiSummaryCard.test.tsx Updates to assert SampleBadge (and removes legacy string assertion).
docs/specs/2026-06-07-v2-ai-p0-pr3a-fe-mode-migration-design.md New design spec for PR3a migration + sample-label treatment.
docs/plans/2026-06-07-v2-ai-p0-pr3a-fe-mode-migration.md New detailed implementation plan for PR3a tasks.
.ai/docs/architectural-invariants.md Updates AI invariant to tri-state model + SampleBadge requirement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/components/Settings/panes/AppearancePane.tsx
Comment thread frontend/src/components/PrDetail/OverviewTab/AiSummaryCard.tsx
Comment thread .ai/docs/architectural-invariants.md Outdated
Comment thread frontend/src/components/ActivityRail/ActivityRail.tsx
Copilot AI review requested due to automatic review settings June 8, 2026 10:08

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.

Pull request overview

Copilot reviewed 47 out of 52 changed files in this pull request and generated 3 comments.

Comment thread frontend/src/components/PrDetail/OverviewTab/AiSummaryCard.tsx
Comment thread .ai/docs/architectural-invariants.md Outdated
Copilot AI review requested due to automatic review settings June 8, 2026 10:37

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.

Pull request overview

Copilot reviewed 49 out of 54 changed files in this pull request and generated 6 comments.

Comment thread frontend/src/components/Settings/panes/AppearancePane.tsx
Comment thread frontend/src/components/Ai/SampleBadge.tsx
Comment thread frontend/src/components/PrDetail/OverviewTab/AiSummaryCard.module.css Outdated
Comment thread frontend/src/styles/tokens.css Outdated
Comment thread frontend/src/hooks/useCapabilities.ts Outdated
Comment thread frontend/src/hooks/useAiGate.ts
@prpande

prpande commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

Follow-ups filed (non-blocking)

Still deferred

  • PR3b egress-consent gate — a hard prerequisite before any P1 work registers a real Live seam (see the Security Impact section above). Filing its tracking issue is intentionally deferred to PR3b planning.

@prpande

prpande commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Jun 8, 2026

Copy link
Copy Markdown

Claude finished @prpande's task in 3m 41s —— View job


PR Review: feat(ai): PR3a — FE tri-state ui.aiMode migration + define-once SampleBadge

Tasks

  • Set up task list
  • Read key source files and diff
  • Review hooks and context changes
  • Review SampleBadge component
  • Review AppearancePane / SegmentedControl
  • Review test coverage
  • Review architecture/docs updates
  • Compile and post findings

Summary

This is a well-scoped, clearly documented migration. The architecture is sound — the two-factor gate (capabilities[key] && aiMode !== 'off') is correctly forward-compat for D112, SampleBadge as a self-gating define-once marker is the right pattern, and the usePreferences rollback under POST failure is race-safe. Both Copilot review rounds were fully addressed. The items below are mostly comment hygiene and minor observations.


Stale Comments (non-blocking, but worth cleaning before merge)

1. frontend/src/api/types.ts:59 — references the old field name:

// shape: since #221 the SPA derives capabilities from the shared aiPreview
// preference (useCapabilities) rather than calling this endpoint

Should read aiMode preference. Fix this →

2. frontend/src/components/Ai/AiComposerAssistant.tsx:5-6 — comment still says preferences.aiPreview via useAiGate:

// capabilities.composerAssist + preferences.aiPreview via useAiGate,

Fix this →

3. frontend/src/components/PrDetail/SubmitDialog/PreSubmitValidatorCard.tsx:8:

// returns []; under aiPreview the slot renders frontend-side canned data —

Fix this →

4. frontend/src/components/PrDetail/SubmitDialog/SubmitDialog.tsx:55:

// Canned validator results when aiPreview is on; [] otherwise

Fix this →


Design Observations (no action required)

SampleBadge self-gating via hook: SampleBadge calls useIsSampleMode() directly, which subscribes to usePreferences. With 7+ badge instances in Preview mode, this creates multiple context subscriptions — but since they all share one provider, the overhead is negligible. The self-gating pattern is clean and the define-once intent is well served.

InboxPage region badge placement: The {showCategoryChip && <SampleBadge variant="region" />} at InboxPage.tsx:85 floats between the toolbar and the section list rather than inside a section header. The other region badges (ActivityRail, FileTree) sit inside a titled <header>. This is a minor visual-hierarchy inconsistency — not a bug, but something to revisit if the inbox layout gets a P1 header element.

Live mode visual ambiguity: A hand-edited ui.ai.mode=live config shows the Preview radio as selected with no indication the underlying value is live. Clicking Preview is a no-op (correctly guarded in df63bdf). This is intentional for P0 but worth noting in the PR3b scope since Live will become user-visible.


Test Coverage

Coverage is comprehensive. One minor gap:

SampleBadge.test.tsx doesn't exercise the solid variant — AiHunkAnnotation uses <SampleBadge solid /> but there's no assertion that the solid CSS class is applied. The current four tests cover inline (default), region, off mode, and live mode. Low risk since the CSS class application follows the same filter+join pattern as region, but a one-liner it would close the gap. Fix this →


Security

The egress-consent callout is correct and the current P0 safeguard (Live → AllOff locally) is an appropriate latent-risk mitigation. The hard prerequisite tracking for PR3b is clear. No other security concerns.


Verdict

Approves with the stale comments noted. All Copilot round-1 and round-2 findings were addressed. The architecture (tri-state gate, define-once badge, reactive preferences rollback) is clean and the forward-compat shape for D112 is correctly scaffolded. The four stale aiPreview comment references are the only remaining housekeeping — straightforward fixes that can land in a follow-up commit or be batched with any other in-flight changes.

Copilot AI review requested due to automatic review settings June 8, 2026 11:39
@prpande

prpande commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

Thanks — addressed in a0068761:

  • Stale aiPreview comments — refreshed all 4 flagged (types.ts:59, AiComposerAssistant.tsx, PreSubmitValidatorCard.tsx, SubmitDialog.tsx) to aiMode/ui.aiMode, plus AskAiButton.tsx:4 (same stale phrasing) so the theme is fully closed.
  • solid variant test — added to SampleBadge.test.tsx (now 5 cases: inline / region / solid / off / live).
  • Design observations — acknowledged as no-action for P0. The InboxPage region-badge-placement note (floats between toolbar and section list vs inside a <header>) is logged for PR3b's inbox-header work; the Live visual-ambiguity point is already in the PR3b scope.

Verified via direct-binary prettier + eslint + build (clean) and SampleBadge.test.tsx 5/5. No pixel changes, so no baseline impact.

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.

Pull request overview

Copilot reviewed 51 out of 56 changed files in this pull request and generated 1 comment.

Comment thread frontend/e2e/inbox.spec.ts
@prpande
prpande merged commit 6357692 into V2 Jun 8, 2026
4 checks passed
@prpande
prpande deleted the feat/v2-ai-pr3-fe-mode-migration branch June 8, 2026 12:41
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