Skip to content

feat(console): cards, actions, mentions + stripe surface nav + live doc wire - #60

Closed
Travis-Gilbert wants to merge 8 commits into
mainfrom
claude/console-cards-mentions-nav
Closed

feat(console): cards, actions, mentions + stripe surface nav + live doc wire#60
Travis-Gilbert wants to merge 8 commits into
mainfrom
claude/console-cards-mentions-nav

Conversation

@Travis-Gilbert

@Travis-Gilbert Travis-Gilbert commented Jul 17, 2026

Copy link
Copy Markdown
Owner

What

HANDOFF-CARDS-ACTIONS-MENTIONS (K1-K7) plus fixes surfaced in live review.

Cards, actions, mentions

  • K1 Card templates as data (person/task/generic seeds) and one card engine (card.full / card.compact) rendering any kind through the block contract. A malformed or missing template degrades to the generic card with a note, never an error.
  • K2 Inspector leads with the compact card above the field table; cards.grid renders an ObjectQuery as virtualized faces at Twenty density; a Cards surface joins the seeded screens.
  • K3 Action sheet with three entries into one identical sheet: /do in the composer, the todo-block arrow + Alt+Enter on Galley task items, and the Action verb on inspector and cards. Staged context is always visible; auto-suggest adds removable chips; save-as-rule names IX6.
  • K4 Delegate wire: For me posts the pack to /api/harness/delegate and names the harness refusal; With me stages visible object references above the thread composer.
  • K5 Mentions detection engine (crates/commonplace/src/mentions.rs): exact and normalized-exact tiers, the recorded span slices back to the original atom text, incremental on ingest and on object create/update, deterministic candidate id as the dedup and dismissal-suppression key.
  • K6 Mentions surface: linked/unlinked counts, passage list with the matched span highlighted, confirm writes a MENTIONED_IN edge carrying its basis, dismiss records the negative signal.
  • K7 Gates: template render-model tests, the pack-equals-chips invariant test (the named invariant of the round), the candidate-to-confirmed-edge round trip, and cards/actions/mentions e2e with visual baselines.

Live-review fixes

  • Screen navigation moves out of the toolbar dropdown into the leftmost stripe (the AMENDMENT's stripe surfaces group): surfaces on top, a divider, then the active surface's tool windows, one bar in the original chrome tokens. The toolbar shows the active screen as a quiet breadcrumb.
  • Document and code editing rides the live object wire so edits persist to the backend. Content rides as extra properties (markdown / content) matching the view projection; the console filters client-side so slug/id predicates hold; the backend is seeded once, idempotent by slug and path.

Verify

40 crate tests, 29 console unit tests, 25 e2e, and the fence, register, contrast, and motion gates all pass locally.

Notes

  • Supersedes the small omnibar Cmd/Ctrl+K change in fix(console): browser-reliable omnibar Ask key (Cmd/Ctrl+K) #59 is NOT included here; that stays its own PR.
  • Card template editing and grid drag-reorder are the Model surface (N5, next: HANDOFF-DIAGRAM-REGISTERS-AND-MODEL); reorder needs an order field the record model does not yet carry.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Cards surface with templated person/task/project/org cards, a responsive grid, related-record chips, progress indicators, and generic fallbacks.
    • Introduced an action sheet for handing off work from cards, /do commands, and document todos (with staged context chips).
    • Added mention detection UI with Confirm and Dismiss workflows.
    • Seeded editable documents and extended workspace content, including a “Console punch list” doc.
  • Accessibility & Performance

    • Added Ctrl/Cmd+K support for opening Ask.
    • Improved keyboard flow, reduced-motion handling for the action sheet, and optimized large grid rendering via virtualization.

Cmd/Ctrl+L (handoff named choice 3, Cursor muscle memory) is a browser
accelerator reserved for the address bar, so a browser tab never delivers
the keydown to the page and the omnibar cannot capture it. It works in the
desktop shell but not in a browser. Add Cmd/Ctrl+K (the command-palette
convention browsers do deliver) as an additional Ask trigger, keeping
Cmd/Ctrl+L for the desktop target and double-Shift for Search. The field's
aria-keyshortcuts advertises all of them. e2e asserts Ctrl+K opens Ask.
…oc wire

HANDOFF-CARDS-ACTIONS-MENTIONS K1-K7:
- K1 card templates as data (person/task/generic seeds) + one card engine
  (card.full / card.compact) rendering any kind through the block contract;
  malformed or missing template degrades to generic with a note, never errors.
- K2 mounts: inspector leads with the compact card above the field table;
  cards.grid descriptor renders an ObjectQuery as virtualized faces at Twenty
  density; a Cards surface joins the seeded screens.
- K3 action sheet, three entries into one sheet (/do composer, todo-block
  arrow + Alt+Enter on Galley task items, Action verb on inspector and cards);
  staged context is always visible; auto-suggest adds removable chips; save as
  rule names IX6.
- K4 delegate wire: For me posts the pack to /api/harness/delegate and names
  the harness refusal; With me stages visible object refs above the composer.
- K5 mentions detection engine (crates/commonplace/src/mentions.rs): exact and
  normalized-exact tiers, span slices back to the original atom text,
  incremental on ingest and on object create/update, deterministic candidate
  id as the dedup and dismissal-suppression key.
- K6 mentions surface: linked/unlinked counts, passage list with the matched
  span highlighted, confirm writes the MENTIONED_IN edge with its basis on the
  edge, dismiss records the negative signal.
- K7 gates: template render-model tests, the pack-equals-chips invariant test,
  the candidate-to-confirmed-edge round trip, and cards/actions/mentions e2e
  with visual baselines.

Follow-up fixes from live review:
- Screen navigation moves out of the toolbar dropdown into the leftmost stripe
  (the stripe surfaces group): surfaces on top, a divider, then the active
  surface's tool windows, one bar in the original chrome tokens. The toolbar
  shows the active screen as a quiet breadcrumb.
- Documents and code files ride the live object wire so edits persist to the
  backend; content rides as extra properties (markdown / content) matching the
  view projection, the console filters client-side so slug/id predicates hold,
  and the backend is seeded once (idempotent by slug/path).

Verify: 40 crate tests, 29 console unit tests, 25 e2e, and the fence, register,
contrast, and motion gates all pass.
Copilot AI review requested due to automatic review settings July 17, 2026 18:57
@ecc-tools

ecc-tools Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Travis-Gilbert, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b5a49d64-6c8a-4af4-81d6-5d0e61ce3737

📥 Commits

Reviewing files that changed from the base of the PR and between 37ad956 and 092f32a.

⛔ Files ignored due to path filters (7)
  • apps/console/e2e/cards.spec.ts-snapshots/action-sheet-linux.png is excluded by !**/*.png
  • apps/console/e2e/cards.spec.ts-snapshots/card-compact-inspector-linux.png is excluded by !**/*.png
  • apps/console/e2e/cards.spec.ts-snapshots/cards-grid-linux.png is excluded by !**/*.png
  • apps/console/e2e/omnibar.spec.ts-snapshots/omnibar-expanded-ask-linux.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1280-dark-linux.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1440-dark-linux.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1440-reduced-motion-linux.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • apps/console/e2e/hunk-review.spec.ts
  • apps/console/e2e/omnibar.spec.ts
  • apps/console/src/components/shell/Omnibar.tsx
📝 Walkthrough

Walkthrough

The console adds template-driven card surfaces, virtualized grids, relation inspection, action-sheet handoff, staged context, incremental mentions, surface-rail navigation, and Ctrl/Cmd+K Ask access. E2E fixtures and backend integrations support the new flows.

Changes

Console cards and domain data

Layer / File(s) Summary
Card templates and virtualized views
apps/console/src/lib/card-templates.ts, apps/console/src/views/CardView.tsx, apps/console/src/views/registry.tsx
Adds validated templates, generic fallbacks, gauges, relation chips, full cards, and virtualized grids.
Console-hosted card data
apps/console/src/lib/console-host.ts, apps/console/src/lib/workspace-seed.ts
Seeds card templates and the Cards surface, supports live-domain queries, and seeds editable documents.
Card validation and E2E coverage
apps/console/src/lib/card-templates.test.ts, apps/console/e2e/cards.spec.ts
Tests template behavior, rendering, relation navigation, virtualization, action flows, mentions, and reduced motion.

Action handoff

Layer / File(s) Summary
Action pack and staged context
apps/console/src/lib/action-pack.ts, apps/console/src/lib/shell-store.ts, apps/console/src/lib/thread-store.ts
Defines serialized action packs, context chips, expanded record selection, and staged thread references.
Action sheet entry points
apps/console/src/components/shell/ActionSheet.tsx, apps/console/src/components/ConsoleApp.tsx, apps/console/src/views/RecordInspector.tsx, apps/console/src/views/GalleyDocView.tsx
Adds action-sheet submission, /do interception, inspector actions, and document todo actions.
Harness delegation
apps/console/src/app/api/harness/delegate/route.ts
Forwards action packs to the configured harness and propagates upstream responses.

Incremental mentions

Layer / File(s) Summary
Mention detection and confirmation
crates/commonplace/src/mentions.rs, crates/commonplace/src/block_view.rs, crates/commonplace/src/ingest.rs
Detects exact and normalized aliases, persists candidates, preserves dismissal state, and writes identity edges after confirmation.
Mention UI
apps/console/src/views/MentionsSection.tsx, apps/console/src/views/CardView.tsx, apps/console/e2e/cards.spec.ts
Displays counts and highlighted spans and supports candidate confirmation and dismissal.

Shell navigation and test infrastructure

Layer / File(s) Summary
Surface rail and omnibar
apps/console/src/components/shell/IntuiShell.tsx, apps/console/src/components/shell/MainToolbar.tsx, apps/console/src/components/shell/Omnibar.tsx, apps/console/src/components/shell/icons.tsx
Replaces layout-switcher navigation with a surface rail, stable panel ordering, and Ctrl/Cmd+K Ask access.
Stub API and e2e environment
apps/console/e2e/stub-data-api.mjs, apps/console/playwright.config.ts, apps/console/e2e/proof-workspace.spec.ts, .railwayignore
Adds pooled fixtures, stateful actions, chat configuration, composer assertions, and deployment ignore patterns.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CardView
  participant ActionSheet
  participant DelegateRoute
  participant ConsoleHarness
  User->>CardView: select card and open action
  CardView->>ActionSheet: provide context chips
  ActionSheet->>DelegateRoute: submit action pack
  DelegateRoute->>ConsoleHarness: forward handoff request
  ConsoleHarness-->>ActionSheet: return handoff status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: cards, actions, mentions, stripe navigation, and live document wiring.
Docstring Coverage ✅ Passed Docstring coverage is 86.96% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/console-cards-mentions-nav

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b690111c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +120 to +124
onClose();
requestAnimationFrame(() => {
document
.querySelector<HTMLTextAreaElement>('[data-thread-composer-input]')
?.focus();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve visible With-me context when no thread is mounted

When a user chooses With me from the Cards surface, that surface has no ThreadView or composer, so this closes the only visible chip set and the focus query finds no target. The staged refs remain only in Zustand until the user manually navigates to Workspace; with NEXT_PUBLIC_CONSOLE_CHAT_URL unset, ThreadView returns its unavailable state and they cannot be viewed, removed, or sent at all. Navigate to/open a thread surface or keep a visible staging affordance before closing the sheet.

Useful? React with 👍 / 👎.

Comment on lines +109 to +111
const before = snippet.slice(0, snippetStart);
const match = snippet.slice(snippetStart, snippetEnd);
const after = snippet.slice(snippetEnd);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Convert Rust character offsets before slicing snippets

The mention backend explicitly records snippet_start and snippet_end as Rust character offsets, but JavaScript String.slice consumes UTF-16 code-unit offsets. For any passage with an astral Unicode character before the mention, this highlights the wrong substring (and can split a surrogate pair), even though the recorded span is valid; convert the offsets to JS string indices or slice via code points before rendering.

Useful? React with 👍 / 👎.

Comment on lines +202 to +204
export function UnlinkedMentionsChip({ host, object }: { host: BlockHost; object: ObjectRef }) {
const { candidates } = useMentionCandidates(host, object.id);
const unlinked = candidates.filter((entry) => entry.status === 'unlinked').length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh the header mention chip after status changes

Confirming or dismissing a candidate only calls refresh() on the MentionsSection hook instance. The header chip creates a separate useMentionCandidates instance here, while HttpBlockHost subscriptions are a no-op, so its unlinked total remains stale until some unrelated parent rerender. This leaves the card's visible mention count incorrect immediately after the user resolves a mention.

Useful? React with 👍 / 👎.

Comment on lines +109 to +111
// Seed the backend's document fixtures once so the Documents surface has
// editable, persistent content (the file-editing wire).
void host.ensureSeedContent();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wait for seeded documents before mounting live views

On a fresh object API, the mounted document views query before this fire-and-forget seeding operation completes. queryLiveDomain returns an empty set with a no-op subscription, so ViewInstanceHost keeps its empty result after the seed creates the documents; the initial Console brief can remain blank until the user remounts the view or reloads. Seed before rendering document queries, or notify/refetch the affected views after creation.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds the “cards/actions/mentions” round to the console and backend: incremental unlinked-mention detection (with confirm/dismiss), a kind-templated card rendering engine + cards surface/grid, and a unified action sheet that stages visible context and delegates to the harness. It also moves screen navigation into the left stripe and wires document/code editing to persist through the live object seam (with idempotent seed-to-backend bootstrapping), backed by new unit/e2e coverage.

Changes:

  • Add Rust mentions engine producing mention-candidate objects, plus confirmation writing MENTIONED_IN edges; hook evaluation into ingest and object create/update.
  • Add console card template data model + card renderer (card.full / card.compact) + cards.grid, plus mentions UI surfaces and inspector integration.
  • Add action sheet (three entry points), With-me staging into thread, For-me harness delegation route, surface-rail navigation, and expanded e2e fixtures/tests.

Reviewed changes

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

Show a summary per file
File Description
crates/commonplace/src/mentions.rs New incremental unlinked-mention detection + edge confirmation and tests
crates/commonplace/src/lib.rs Export mentions module
crates/commonplace/src/ingest.rs Trigger mention evaluation during ingest
crates/commonplace/src/block_view.rs Trigger mention evaluation on create/update; confirm-hook for candidates
apps/console/src/views/ThreadView.tsx Show staged With-me refs above composer
apps/console/src/views/registry.tsx Register card.full / cards.grid view descriptors
apps/console/src/views/RecordInspector.tsx Add compact card header + action entry + cross-kind fetch hinting
apps/console/src/views/MentionsSection.tsx Mentions surface UI (counts, passages, confirm/dismiss)
apps/console/src/views/GalleyDocView.tsx Doc switching state reset + todo action affordance integration
apps/console/src/views/CardView.tsx Card engine rendering + grid virtualization + action entry + mentions embedding
apps/console/src/motion/motion-tokens.ts Motion inventory row for action sheet
apps/console/src/lib/workspace-seed.ts Seed Cards surface + punch-list doc
apps/console/src/lib/thread-store.ts With-me staged refs + include them in outbound message text
apps/console/src/lib/shell-store.ts Action sheet origin/chip model + inspector selection caching + helpers
apps/console/src/lib/console-host.ts Live-wire docs/code + seed-to-backend bootstrap + card-template local pool
apps/console/src/lib/console-host.test.ts Update layout seed expectations (Cards surface)
apps/console/src/lib/card-templates.ts Card template schema/validation + seed templates + resolution logic
apps/console/src/lib/card-templates.test.ts Unit tests for card template parsing/resolution
apps/console/src/lib/action-pack.ts Pack serialization + “pack equals chips” invariant probe
apps/console/src/lib/action-pack.test.ts Unit tests for pack/chips invariant
apps/console/src/components/shell/Omnibar.tsx Add Ctrl/Cmd+K shortcut + updated aria-keyshortcuts
apps/console/src/components/shell/MainToolbar.tsx Remove toolbar surface switcher; add breadcrumb naming active surface
apps/console/src/components/shell/IntuiShell.tsx Add surface-rail nav group + mount ActionSheet
apps/console/src/components/shell/icons.tsx Add new surface-nav icons (Workspace/Cards/Model)
apps/console/src/components/shell/ActionSheet.tsx New action sheet UI (autosuggest, destination, delegate, With-me staging)
apps/console/src/components/ConsoleApp.tsx Intercept /do to open sheet + ensure seed content on startup
apps/console/src/app/api/harness/delegate/route.ts New harness delegate passthrough API route
apps/console/playwright.config.ts Configure chat URL for e2e so composer is live
apps/console/e2e/stub-data-api.mjs Expand stub data API with domain fixtures + mentions + docs/code pools
apps/console/e2e/proof-workspace.spec.ts Update expectations for live composer in e2e
apps/console/e2e/omnibar.spec.ts Add Ctrl+K test + switch surface nav to stripe rail
apps/console/e2e/cards.spec.ts New e2e suite for cards/actions/mentions + baselines
.railwayignore Ignore additional build/test artifacts for Railway deploys

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

Comment on lines +59 to +71
export function packEqualsChips(pack: ActionPack, chips: readonly StagedContextChip[]): boolean {
if (pack.context.length !== chips.length) return false;
const key = (entry: PackContextEntry) =>
`${entry.kind}|${entry.label}|${entry.object_id ?? ''}|${entry.text ?? ''}`;
const packKeys = pack.context.map(key).sort();
const chipKeys = chips.map((chip) => key(entryFromChip(chip))).sort();
if (packKeys.some((value, index) => value !== chipKeys[index])) return false;
const firstOrigin = chips.find((chip) => chip.source === 'origin');
if (firstOrigin && pack.context.length > 0) {
return key(pack.context[0]) === key(entryFromChip(firstOrigin));
}
return true;
}
Comment on lines +35 to +40
export function ActionSheet({ host }: { host: BlockHost }) {
const origin = useShellStore((state) => state.actionSheetOrigin);
const close = useShellStore((state) => state.closeActionSheet);
if (!origin) return null;
return <ActionSheetOpen key={origin.chips[0]?.id ?? 'blank'} host={host} onClose={close} />;
}
Comment on lines +33 to +38
const [docId, setDocId] = useState<string | undefined>(doc?.id);
if (doc && doc.id !== docId) {
setDocId(doc.id);
setText(typeof doc.properties.markdown === 'string' ? doc.properties.markdown : '');
setMode('read');
}
Comment on lines +90 to +95
const staged = get().staged;
const refLine = staged
.map((ref) => (ref.objectId ? `@[${ref.label}](${ref.objectId})` : ref.label))
.join(' ');
const text = refLine ? `${rawText}\n${refLine}` : rawText;
if (staged.length > 0) set({ staged: [] });
Comment on lines +961 to +965
} else {
// An edited atom re-evaluates incrementally; existing
// candidate ids (any status) stay untouched.
self.evaluate_mentions_for_atom(&item.id)?;
}

@coderabbitai coderabbitai 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.

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (22)
apps/console/src/components/shell/IntuiShell.tsx-161-169 (1)

161-169: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Commit surface switches atomically or serialize them.

Each click launches multiple unawaited host.emit operations. Rapid clicks or a failed receipt can leave multiple—or no—surfaces active, after which activeSurfaceId selects the wrong persisted surface. Route this through one atomic host action, or queue the complete switch and handle failures before accepting another request.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/IntuiShell.tsx` around lines 161 - 169,
Update switchTo in IntuiShell so changing the active surface is atomic or
serialized as one complete operation rather than issuing multiple unawaited
host.emit calls. Ensure rapid requests cannot overlap, and handle a failed
switch before accepting another request so the persisted state always has
exactly the requested surface active.
apps/console/e2e/stub-data-api.mjs-254-254 (1)

254-254: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return shape metadata matching the selected pools.

Line 254 can now return person, task, doc, and other types, but Lines 272-274 still describe every result as a record with record-only fields. This violates the query contract and can select an incorrect renderer. Derive shape.types and shape.fields from the query/result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/stub-data-api.mjs` at line 254, Update the query result
construction around poolFor and the response shape metadata so shape.types and
shape.fields are derived from query.types and the selected objects rather than
always using record-only metadata. Preserve the existing result data while
ensuring person, task, doc, and other pools advertise their correct types and
fields for renderer selection.
apps/console/e2e/stub-data-api.mjs-307-317 (1)

307-317: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject updates when the target does not exist.

A missing target falls through to the generic accepted receipt even though this stub will never apply the update later. Return a non-success response so broken IDs and lost document edits fail the E2E flow immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/stub-data-api.mjs` around lines 307 - 317, Update the
action.kind === 'update' handling to return a non-success response when
allStored().find cannot locate the requested target. Preserve the existing
successful response and mutation for found targets, and prevent missing-target
updates from falling through to the generic accepted receipt.
apps/console/e2e/stub-data-api.mjs-320-327 (1)

320-327: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make seed creates idempotent.

IDs based on pool.length + 1 are deterministic only for the current mutable state. Retrying the same seed request appends another object, changing cardinality and captures. Upsert using a stable logical key or action-provided id.

As per PR objectives, backend seeding must be idempotent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/stub-data-api.mjs` around lines 320 - 327, The create
handling for POOLS in the action-processing flow must be idempotent: derive a
stable logical key from the action or its provided id, detect an existing pool
entry, and update/reuse it instead of appending a duplicate on retries. Preserve
the existing response shape and return the stable object id in target_ids.
apps/console/src/views/MentionsSection.tsx-134-139 (1)

134-139: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clear pending state when host.emit fails.

A rejected update skips Line 137, leaving that candidate disabled indefinitely and producing an unhandled rejection. Use try/catch/finally and expose a retryable error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/MentionsSection.tsx` around lines 134 - 139, Update
setStatus to handle rejected host.emit updates with try/catch/finally: always
clear the candidate via setPending(null), expose a retryable error to the user,
and prevent an unhandled rejection while preserving refresh only for successful
updates.
apps/console/src/views/MentionsSection.tsx-59-103 (1)

59-103: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Invalidate every mounted mention query after updates.

refresh() only updates the hook instance that initiated the action. Confirming in the inspector leaves the separately mounted card chip showing its old unlinked count. Use shared query invalidation or a host subscription.

Also applies to: 134-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/MentionsSection.tsx` around lines 59 - 103, Update
useMentionCandidates so refresh invalidates every mounted mention query, not
just the initiating hook instance. Replace the local generation-only mechanism
with the existing shared query invalidation or host subscription mechanism, and
ensure all mounted instances reload after mention updates while preserving the
current query and retry behavior.
crates/commonplace/src/mentions.rs-254-292 (1)

254-292: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reconcile candidates when an atom is edited.

Re-evaluation only creates missing IDs. If body text removes a mention or moves its span, the existing candidate and captured snippet remain unchanged and can later create an edge from stale evidence. Reconcile unlinked candidates against the current matches and define removal/update behavior for confirmed edges.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/commonplace/src/mentions.rs` around lines 254 - 292, Update
evaluate_mentions_for_atom to reconcile existing candidates for the edited atom
with the current match_aliases results, removing candidates whose matches
disappeared and updating candidates whose spans or captured snippets changed
instead of skipping existing IDs. Preserve confirmed mentions/edges according to
the project’s established behavior, explicitly preventing stale candidate
evidence from producing edges while retaining confirmed edge semantics.
apps/console/src/app/api/harness/delegate/route.ts-19-31 (1)

19-31: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the upstream handoff request with a timeout.

A stalled harness can leave this route pending indefinitely, consuming server resources and leaving the sheet stuck in its submitting state. Add an abort timeout and map it to the unavailable response.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/app/api/harness/delegate/route.ts` around lines 19 - 31,
Update the upstream fetch in the delegate route to use an AbortController-based
timeout, ensuring stalled handoff requests are aborted after the configured
limit. Catch the timeout/abort condition and map it to the existing unavailable
response while preserving normal upstream response handling.
apps/console/src/views/MentionsSection.tsx-43-55 (1)

43-55: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Slice snippets using the backend’s character-offset semantics.

Rust records Unicode character offsets, but JavaScript indexes strings by UTF-16 code units. Emoji or other non-BMP characters before the match shift the highlight. Clamp and slice Array.from(snippet) instead.

Proposed fix
   const snippet = typeof props.snippet === 'string' ? props.snippet : '';
+  const snippetLength = Array.from(snippet).length;
   const start = typeof props.snippet_start === 'number' ? props.snippet_start : 0;
   const end = typeof props.snippet_end === 'number' ? props.snippet_end : 0;
...
-    snippetStart: Math.max(0, Math.min(start, snippet.length)),
-    snippetEnd: Math.max(0, Math.min(end, snippet.length)),
+    snippetStart: Math.max(0, Math.min(start, snippetLength)),
+    snippetEnd: Math.max(0, Math.min(end, snippetLength)),
...
-  const before = snippet.slice(0, snippetStart);
-  const match = snippet.slice(snippetStart, snippetEnd);
-  const after = snippet.slice(snippetEnd);
+  const chars = Array.from(snippet);
+  const before = chars.slice(0, snippetStart).join('');
+  const match = chars.slice(snippetStart, snippetEnd).join('');
+  const after = chars.slice(snippetEnd).join('');

Also applies to: 106-111

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/MentionsSection.tsx` around lines 43 - 55, Update the
snippet offset handling in the mention mapping logic to interpret snippet_start
and snippet_end as Unicode character offsets: convert snippet to
Array.from(snippet), clamp both offsets to that array’s length, and slice using
the clamped values. Apply the same character-based slicing to the related
snippet-highlight logic near the other referenced range.
crates/commonplace/src/mentions.rs-58-72 (1)

58-72: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve alias identity in the candidate ID.

alias_slug() is lossy: aliases such as A.B and A B produce the same ID. One candidate can therefore overwrite or suppress another, including its dismissal state. Use a collision-resistant encoding or hash of the original alias.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/commonplace/src/mentions.rs` around lines 58 - 72, Update
mention_candidate_id and alias_slug so the candidate ID preserves distinct
original aliases instead of using the lossy slug alone. Replace the alias
component with a collision-resistant encoding or hash derived from the full
alias, ensuring aliases such as “A.B” and “A B” produce different deterministic
IDs while retaining deduplication and dismissal-state behavior.
apps/console/src/views/CardView.tsx-362-380 (1)

362-380: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not wrap mention controls in a card-wide button.

Compact cards contain mention buttons inside this role="button" wrapper. Their clicks bubble to selectRecord, so expanding, confirming, or dismissing a mention also opens the record; the nested interactive semantics are invalid as well. Use a dedicated Open control or a non-interactive wrapper with explicitly separated actions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/CardView.tsx` around lines 362 - 380, The CardGridCell
wrapper currently makes the entire card a role="button", causing nested mention
controls in RecordCard to trigger selectRecord and creating invalid interactive
nesting. Replace the card-wide interactive behavior with a dedicated Open
control or a non-interactive container, and ensure mention actions remain
isolated while preserving keyboard access to opening the record.
apps/console/src/lib/console-host.ts-292-320 (1)

292-320: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve complete and live query semantics.

This wrapper only fetches the first 500 backend objects and replaces the backend subscription with a no-op. Documents beyond that page become unqueryable by slug/path, while live: true consumers never receive updates. Follow next_cursor and reapply the local transform to subscribed/refetched results.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/console-host.ts` around lines 292 - 320, The
queryLiveDomain implementation must preserve complete results and live updates
instead of limiting reads to the first backend page and returning a no-op
subscription. Update queryLiveDomain to follow next_cursor across all http.query
pages, reapply predicate filtering, ranking, and pagination to the combined
objects, and use the backend subscription/refetch mechanism so live:true
consumers receive updates.
apps/console/src/lib/console-host.ts-328-358 (1)

328-358: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Move fixture seeding to an atomic backend upsert.

The single-page read-before-create check is not idempotent: an existing slug/path outside the first 200 results is missed, and concurrent console clients can both observe absence and create duplicates. Seed through deterministic IDs or a backend uniqueness/upsert operation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/console-host.ts` around lines 328 - 358, The
ensureSeedContent method currently performs a limited read-before-create flow
that can miss existing fixtures and race with other clients. Replace the
separate document and code-file query/create loops with an atomic backend upsert
or deterministic-ID seeding operation, using each document slug and code-file
path as the stable identity so repeated and concurrent calls cannot create
duplicates.
crates/commonplace/src/block_view.rs-961-965 (1)

961-965: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reconcile stale mention candidates after atom edits.

evaluate_mentions_for_atom only inserts missing IDs. Removing or moving a mention therefore leaves stale candidates/snippets—and any confirmed MENTIONED_IN edge—attached to the edited atom. Re-evaluation should update surviving candidates and retire candidates/edges no longer supported by the text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/commonplace/src/block_view.rs` around lines 961 - 965, Update the
edited-atom branch around evaluate_mentions_for_atom so re-evaluation reconciles
the atom’s complete mention state rather than only inserting missing candidate
IDs. Refresh surviving candidate snippets, remove candidates no longer supported
by the current text, and retire any corresponding confirmed MENTIONED_IN edges;
preserve existing candidates only when still valid.
apps/console/src/views/registry.tsx-114-142 (1)

114-142: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Declare the update action emitted by card views.

Both renderers mount mention controls that emit candidate status updates, but their descriptors omit update. Add it to both emits lists so registry capability metadata matches runtime behavior.

Proposed fix
-  emits: ['select', 'open'],
+  emits: ['select', 'open', 'update'],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/registry.tsx` around lines 114 - 142, Update the emits
lists in CARD_FULL and CARDS_GRID to include the update action alongside select
and open, so both card view descriptors accurately declare the status-update
events emitted by their renderers.
apps/console/src/components/shell/ActionSheet.tsx-128-149 (1)

128-149: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent duplicate delegation requests while submission is pending.

Ctrl/Cmd+Enter remains active after submit.kind becomes submitting, allowing repeated non-idempotent POSTs. Guard submitSheet with an in-flight ref or state check.

Also applies to: 181-190, 272-280

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/ActionSheet.tsx` around lines 128 - 149,
The submitSheet callback allows duplicate delegation POSTs while submission is
in progress. Add an in-flight ref or check the current submit state at the start
of submitSheet, returning immediately when the submission is already submitting,
and ensure the guard is cleared when the request completes so later submissions
remain possible.
apps/console/src/views/GalleyDocView.tsx-107-112 (1)

107-112: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Give each todo selection a collision-free chip ID.

Distinct todos in the same document with identical first 24 characters receive the same ID. thread-store.stage() then treats the second todo as already staged and silently drops it. Include the list-item index or another unique identifier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/GalleyDocView.tsx` around lines 107 - 112, Update the
chip ID construction for todo selections in GalleyDocView so it remains unique
for distinct todos within the same document, including when their first 24
characters match. Incorporate the surrounding list-item index or another stable
unique identifier while preserving the existing document and text components.
apps/console/src/views/RecordInspector.tsx-26-29 (1)

26-29: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not expose the previous record while the new selection loads.

After selectedRecordId changes, record still resolves to stale fetched data. The body correctly shows loading, but the Action button hands off that previous object.

Proposed fix
   const record =
     selectedRecordObject && selectedRecordObject.id === selectedRecordId
       ? selectedRecordObject
-      : fetched;
+      : fetched?.id === selectedRecordId
+        ? fetched
+        : null;

Also applies to: 99-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/RecordInspector.tsx` around lines 26 - 29, Update the
record selection logic in RecordInspector so a changed selectedRecordId cannot
fall back to stale fetched data while the new record is loading. Ensure record
is undefined or otherwise unavailable until fetched matches the current
selection, while preserving selectedRecordObject usage for the matching ID and
the existing loading/body behavior.
apps/console/src/components/shell/ActionSheet.tsx-263-269 (1)

263-269: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Implement the advertised rule-saving flow before merging.

The PR objective includes rule saving, but this control explicitly states that the feature is unavailable and provides no action.

Would you like me to help implement or track the missing rules-engine integration?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/ActionSheet.tsx` around lines 263 - 269,
Implement the rule-saving flow for the ActionSheet control instead of rendering
the unavailable disabled message. Replace the data-save-as-rule-unavailable span
with an actionable Save as rule control that invokes the existing rule-engine
integration or the appropriate save handler, preserving the displayed action and
ensuring the IX6 dependency is validated before saving.
apps/console/src/components/shell/ActionSheet.tsx-110-126 (1)

110-126: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not silently discard “Mark handled after” for With-me actions.

The checkbox remains selectable, but the With-me branch stages only chip references and drops followUp. Either carry this intent into the thread flow or disable/reset the option for that destination.

Also applies to: 255-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/ActionSheet.tsx` around lines 110 - 126,
The with-me branch in ActionSheet’s destination handling discards the selected
followUp intent when calling stageInThread. Update the with-me flow to either
propagate followUp into the thread staging/handling path or explicitly disable
and reset the option when this destination is selected, ensuring no selectable
state is silently lost while preserving the existing focus and close behavior.
apps/console/src/components/shell/ActionSheet.tsx-151-179 (1)

151-179: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Trap keyboard focus inside the modal.

aria-modal="true" does not enforce focus containment. Tab navigation can escape into the underlying console and activate controls while the sheet remains open. Use a focus-scope implementation or native modal dialog behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/ActionSheet.tsx` around lines 151 - 179,
Update the action sheet component around the dialog motion element and its
parent overlay to trap keyboard focus within the open modal, using the project’s
existing focus-scope implementation or native modal dialog behavior. Preserve
Escape handling, backdrop dismissal, and the existing dialog accessibility
attributes.
apps/console/src/lib/thread-store.ts-85-95 (1)

85-95: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Escape staged references with the mention protocol’s serializer.

Labels containing ], ), backslashes, or newlines produce malformed @[label](objectId) references, so the intended object context may not survive transport. Use one shared round-trip-safe encoder with the mention parser.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/thread-store.ts` around lines 85 - 95, Update send in
the thread store to serialize staged reference labels and object IDs through the
mention protocol’s shared round-trip-safe encoder before constructing refLine.
Reuse the existing encoder paired with the mention parser so characters such as
], ), backslashes, and newlines are escaped consistently; preserve the current
staged-clearing and message assembly behavior.
🟡 Minor comments (3)
apps/console/src/views/MentionsSection.tsx-151-154 (1)

151-154: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pluralize the mention count correctly and update its test.

  • apps/console/src/views/MentionsSection.tsx#L151-L154: render place when the visible count is one.
  • apps/console/e2e/cards.spec.ts#L223-L224: expect “mentioned in 1 place, 0 unlinked.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/MentionsSection.tsx` around lines 151 - 154, Update
the mentions summary in MentionsSection.tsx to use singular “place” when the
visible, non-dismissed count is one and plural “places” otherwise. Update the
corresponding expectation in apps/console/e2e/cards.spec.ts lines 223-224 to
expect “mentioned in 1 place, 0 unlinked.”
apps/console/src/lib/card-templates.ts-67-69 (1)

67-69: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat malformed nested template fields as validation failures.

Invalid subtitle, fact, chip, or gauge entries are silently filtered out, producing an incomplete card without fallbackNote. Return a parse failure when a supplied array contains an invalid entry so malformed templates consistently degrade to generic.

Also applies to: 88-120

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/card-templates.ts` around lines 67 - 69, Update
stringArray and the template parsing flow for subtitle, fact, chip, and gauge
fields so supplied arrays fail validation when any entry is not a string instead
of filtering invalid entries. Propagate the parse failure to the existing
generic fallback path, while preserving successful parsing for arrays whose
entries are all valid strings.
apps/console/src/lib/action-pack.ts-59-68 (1)

59-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include object_type in the exact-context comparison.

A pack can change an object's type while still passing packEqualsChips, violating the stated invariant.

Proposed fix
   const key = (entry: PackContextEntry) =>
-    `${entry.kind}|${entry.label}|${entry.object_id ?? ''}|${entry.text ?? ''}`;
+    `${entry.kind}|${entry.label}|${entry.object_id ?? ''}|${entry.object_type ?? ''}|${entry.text ?? ''}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/action-pack.ts` around lines 59 - 68, Update
packEqualsChips and its local key function to include each PackContextEntry’s
object_type in the generated comparison key, ensuring both sorted exact-context
checks and the first-origin check detect object type changes.
🧹 Nitpick comments (1)
apps/console/e2e/cards.spec.ts (1)

144-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the complete action-pack context exactly.

This test permits reordered entries and duplicate-label substitutions because it checks only length and label membership. Compare the ordered context objects—including IDs/object IDs—to the visible chip model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/cards.spec.ts` around lines 144 - 160, Strengthen the
no-silent-context assertions in the handoff test by comparing the submitted
pack.context to the visible chip model in order, including each chip’s label and
ID/object ID fields. Replace the length and membership-only checks around
visibleChips and pack.context with an exact ordered deep comparison, preserving
the instruction assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Major comments:
In `@apps/console/e2e/stub-data-api.mjs`:
- Line 254: Update the query result construction around poolFor and the response
shape metadata so shape.types and shape.fields are derived from query.types and
the selected objects rather than always using record-only metadata. Preserve the
existing result data while ensuring person, task, doc, and other pools advertise
their correct types and fields for renderer selection.
- Around line 307-317: Update the action.kind === 'update' handling to return a
non-success response when allStored().find cannot locate the requested target.
Preserve the existing successful response and mutation for found targets, and
prevent missing-target updates from falling through to the generic accepted
receipt.
- Around line 320-327: The create handling for POOLS in the action-processing
flow must be idempotent: derive a stable logical key from the action or its
provided id, detect an existing pool entry, and update/reuse it instead of
appending a duplicate on retries. Preserve the existing response shape and
return the stable object id in target_ids.

In `@apps/console/src/app/api/harness/delegate/route.ts`:
- Around line 19-31: Update the upstream fetch in the delegate route to use an
AbortController-based timeout, ensuring stalled handoff requests are aborted
after the configured limit. Catch the timeout/abort condition and map it to the
existing unavailable response while preserving normal upstream response
handling.

In `@apps/console/src/components/shell/ActionSheet.tsx`:
- Around line 128-149: The submitSheet callback allows duplicate delegation
POSTs while submission is in progress. Add an in-flight ref or check the current
submit state at the start of submitSheet, returning immediately when the
submission is already submitting, and ensure the guard is cleared when the
request completes so later submissions remain possible.
- Around line 263-269: Implement the rule-saving flow for the ActionSheet
control instead of rendering the unavailable disabled message. Replace the
data-save-as-rule-unavailable span with an actionable Save as rule control that
invokes the existing rule-engine integration or the appropriate save handler,
preserving the displayed action and ensuring the IX6 dependency is validated
before saving.
- Around line 110-126: The with-me branch in ActionSheet’s destination handling
discards the selected followUp intent when calling stageInThread. Update the
with-me flow to either propagate followUp into the thread staging/handling path
or explicitly disable and reset the option when this destination is selected,
ensuring no selectable state is silently lost while preserving the existing
focus and close behavior.
- Around line 151-179: Update the action sheet component around the dialog
motion element and its parent overlay to trap keyboard focus within the open
modal, using the project’s existing focus-scope implementation or native modal
dialog behavior. Preserve Escape handling, backdrop dismissal, and the existing
dialog accessibility attributes.

In `@apps/console/src/components/shell/IntuiShell.tsx`:
- Around line 161-169: Update switchTo in IntuiShell so changing the active
surface is atomic or serialized as one complete operation rather than issuing
multiple unawaited host.emit calls. Ensure rapid requests cannot overlap, and
handle a failed switch before accepting another request so the persisted state
always has exactly the requested surface active.

In `@apps/console/src/lib/console-host.ts`:
- Around line 292-320: The queryLiveDomain implementation must preserve complete
results and live updates instead of limiting reads to the first backend page and
returning a no-op subscription. Update queryLiveDomain to follow next_cursor
across all http.query pages, reapply predicate filtering, ranking, and
pagination to the combined objects, and use the backend subscription/refetch
mechanism so live:true consumers receive updates.
- Around line 328-358: The ensureSeedContent method currently performs a limited
read-before-create flow that can miss existing fixtures and race with other
clients. Replace the separate document and code-file query/create loops with an
atomic backend upsert or deterministic-ID seeding operation, using each document
slug and code-file path as the stable identity so repeated and concurrent calls
cannot create duplicates.

In `@apps/console/src/lib/thread-store.ts`:
- Around line 85-95: Update send in the thread store to serialize staged
reference labels and object IDs through the mention protocol’s shared
round-trip-safe encoder before constructing refLine. Reuse the existing encoder
paired with the mention parser so characters such as ], ), backslashes, and
newlines are escaped consistently; preserve the current staged-clearing and
message assembly behavior.

In `@apps/console/src/views/CardView.tsx`:
- Around line 362-380: The CardGridCell wrapper currently makes the entire card
a role="button", causing nested mention controls in RecordCard to trigger
selectRecord and creating invalid interactive nesting. Replace the card-wide
interactive behavior with a dedicated Open control or a non-interactive
container, and ensure mention actions remain isolated while preserving keyboard
access to opening the record.

In `@apps/console/src/views/GalleyDocView.tsx`:
- Around line 107-112: Update the chip ID construction for todo selections in
GalleyDocView so it remains unique for distinct todos within the same document,
including when their first 24 characters match. Incorporate the surrounding
list-item index or another stable unique identifier while preserving the
existing document and text components.

In `@apps/console/src/views/MentionsSection.tsx`:
- Around line 134-139: Update setStatus to handle rejected host.emit updates
with try/catch/finally: always clear the candidate via setPending(null), expose
a retryable error to the user, and prevent an unhandled rejection while
preserving refresh only for successful updates.
- Around line 59-103: Update useMentionCandidates so refresh invalidates every
mounted mention query, not just the initiating hook instance. Replace the local
generation-only mechanism with the existing shared query invalidation or host
subscription mechanism, and ensure all mounted instances reload after mention
updates while preserving the current query and retry behavior.
- Around line 43-55: Update the snippet offset handling in the mention mapping
logic to interpret snippet_start and snippet_end as Unicode character offsets:
convert snippet to Array.from(snippet), clamp both offsets to that array’s
length, and slice using the clamped values. Apply the same character-based
slicing to the related snippet-highlight logic near the other referenced range.

In `@apps/console/src/views/RecordInspector.tsx`:
- Around line 26-29: Update the record selection logic in RecordInspector so a
changed selectedRecordId cannot fall back to stale fetched data while the new
record is loading. Ensure record is undefined or otherwise unavailable until
fetched matches the current selection, while preserving selectedRecordObject
usage for the matching ID and the existing loading/body behavior.

In `@apps/console/src/views/registry.tsx`:
- Around line 114-142: Update the emits lists in CARD_FULL and CARDS_GRID to
include the update action alongside select and open, so both card view
descriptors accurately declare the status-update events emitted by their
renderers.

In `@crates/commonplace/src/block_view.rs`:
- Around line 961-965: Update the edited-atom branch around
evaluate_mentions_for_atom so re-evaluation reconciles the atom’s complete
mention state rather than only inserting missing candidate IDs. Refresh
surviving candidate snippets, remove candidates no longer supported by the
current text, and retire any corresponding confirmed MENTIONED_IN edges;
preserve existing candidates only when still valid.

In `@crates/commonplace/src/mentions.rs`:
- Around line 254-292: Update evaluate_mentions_for_atom to reconcile existing
candidates for the edited atom with the current match_aliases results, removing
candidates whose matches disappeared and updating candidates whose spans or
captured snippets changed instead of skipping existing IDs. Preserve confirmed
mentions/edges according to the project’s established behavior, explicitly
preventing stale candidate evidence from producing edges while retaining
confirmed edge semantics.
- Around line 58-72: Update mention_candidate_id and alias_slug so the candidate
ID preserves distinct original aliases instead of using the lossy slug alone.
Replace the alias component with a collision-resistant encoding or hash derived
from the full alias, ensuring aliases such as “A.B” and “A B” produce different
deterministic IDs while retaining deduplication and dismissal-state behavior.

---

Minor comments:
In `@apps/console/src/lib/action-pack.ts`:
- Around line 59-68: Update packEqualsChips and its local key function to
include each PackContextEntry’s object_type in the generated comparison key,
ensuring both sorted exact-context checks and the first-origin check detect
object type changes.

In `@apps/console/src/lib/card-templates.ts`:
- Around line 67-69: Update stringArray and the template parsing flow for
subtitle, fact, chip, and gauge fields so supplied arrays fail validation when
any entry is not a string instead of filtering invalid entries. Propagate the
parse failure to the existing generic fallback path, while preserving successful
parsing for arrays whose entries are all valid strings.

In `@apps/console/src/views/MentionsSection.tsx`:
- Around line 151-154: Update the mentions summary in MentionsSection.tsx to use
singular “place” when the visible, non-dismissed count is one and plural
“places” otherwise. Update the corresponding expectation in
apps/console/e2e/cards.spec.ts lines 223-224 to expect “mentioned in 1 place, 0
unlinked.”

---

Nitpick comments:
In `@apps/console/e2e/cards.spec.ts`:
- Around line 144-160: Strengthen the no-silent-context assertions in the
handoff test by comparing the submitted pack.context to the visible chip model
in order, including each chip’s label and ID/object ID fields. Replace the
length and membership-only checks around visibleChips and pack.context with an
exact ordered deep comparison, preserving the instruction assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9d7ee97-42c3-4d14-9d98-65d5a8ee92f3

📥 Commits

Reviewing files that changed from the base of the PR and between fd80507 and 0b69011.

⛔ Files ignored due to path filters (7)
  • apps/console/e2e/cards.spec.ts-snapshots/action-sheet-darwin.png is excluded by !**/*.png
  • apps/console/e2e/cards.spec.ts-snapshots/card-compact-inspector-darwin.png is excluded by !**/*.png
  • apps/console/e2e/cards.spec.ts-snapshots/cards-grid-darwin.png is excluded by !**/*.png
  • apps/console/e2e/omnibar.spec.ts-snapshots/omnibar-expanded-ask-darwin.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1280-dark-darwin.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1440-dark-darwin.png is excluded by !**/*.png
  • apps/console/e2e/proof-workspace.spec.ts-snapshots/workspace-1440-reduced-motion-darwin.png is excluded by !**/*.png
📒 Files selected for processing (33)
  • .railwayignore
  • apps/console/e2e/cards.spec.ts
  • apps/console/e2e/omnibar.spec.ts
  • apps/console/e2e/proof-workspace.spec.ts
  • apps/console/e2e/stub-data-api.mjs
  • apps/console/playwright.config.ts
  • apps/console/src/app/api/harness/delegate/route.ts
  • apps/console/src/components/ConsoleApp.tsx
  • apps/console/src/components/shell/ActionSheet.tsx
  • apps/console/src/components/shell/IntuiShell.tsx
  • apps/console/src/components/shell/MainToolbar.tsx
  • apps/console/src/components/shell/Omnibar.tsx
  • apps/console/src/components/shell/icons.tsx
  • apps/console/src/lib/action-pack.test.ts
  • apps/console/src/lib/action-pack.ts
  • apps/console/src/lib/card-templates.test.ts
  • apps/console/src/lib/card-templates.ts
  • apps/console/src/lib/console-host.test.ts
  • apps/console/src/lib/console-host.ts
  • apps/console/src/lib/shell-store.ts
  • apps/console/src/lib/thread-store.ts
  • apps/console/src/lib/workspace-seed.ts
  • apps/console/src/motion/motion-tokens.ts
  • apps/console/src/views/CardView.tsx
  • apps/console/src/views/GalleyDocView.tsx
  • apps/console/src/views/MentionsSection.tsx
  • apps/console/src/views/RecordInspector.tsx
  • apps/console/src/views/ThreadView.tsx
  • apps/console/src/views/registry.tsx
  • crates/commonplace/src/block_view.rs
  • crates/commonplace/src/ingest.rs
  • crates/commonplace/src/lib.rs
  • crates/commonplace/src/mentions.rs

@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts on this branch.

…mentions-nav

# Conflicts:
#	apps/console/e2e/stub-data-api.mjs
#	apps/console/src/lib/console-host.ts
#	apps/console/src/views/registry.tsx
@ecc-tools

ecc-tools Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts on this branch.

Resolved in 9095dca. Three files had conflicts:

  • stub-data-api.mjs – kept HEAD's full fixture pools (DOMAIN, MENTION_CANDIDATES, DOCS, CODE_FILES with poolFor/allStored) and added the HUNKS array from main into POOLS, so both cards/mentions and hunk-review e2e fixtures are served.
  • console-host.ts – kept HEAD's live-wire routing logic; hunks already route through this.http.query (they're not consoleLocal), satisfying the H3 wire contract from main.
  • registry.tsx – included both CardFullView/CardGridView (HEAD K1) and HunkReviewView (main) with their respective descriptors.

All 36 console unit tests pass after the merge.

Opening or closing a tool window flashed the whole content area to the
frame and back (a black frame drop), and the reflow read as broken. Cause:
the PanelGroup was keyed on the visible panel set, so every open/close
changed the key and remounted the entire well: the editor and thread tore
down and rebuilt, and the editor's delayed entrance fade re-fired, leaving
the frame showing through for a beat.

Key the group on the active surface only (a real screen switch still
remounts, which is correct), and give each Panel a stable order derived from
the full region lists so react-resizable-panels reconciles a panel appearing
or disappearing in place. The editor node now survives the toggle and the
content reflows without a teardown. Regression test asserts the editor panel
node survives an Alt+9 toggle (reconcile, not remount).
…w surface

The surface-rail navigation (replacing the toolbar layout dropdown) merged
with the Greenfield Hunk Review surface. Reconcile:
- give the review surface its own rail glyph (IconInspector);
- point the hunk-review e2e at the stripe surfaces group instead of the
  removed Layout dropdown;
- the rail now carries five surfaces, not four;
- give the injected-surface card.full assertion headroom so it does not flake
  under a cold, parallel-loaded dev server.

28 e2e, 36 unit, and the fence/register/motion/contrast gates pass on the
merged tree.
@ecc-tools

ecc-tools Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
apps/console/e2e/hunk-review.spec.ts (1)

4-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import Page from @playwright/test.

Instead of using an inline import() type assertion, you can import Page alongside expect and test for better readability.

♻️ Proposed refactor
-import { expect, test } from '`@playwright/test`';
+import { expect, test, type Page } from '`@playwright/test`';

-async function openReview(page: import('`@playwright/test`').Page) {
+async function openReview(page: Page) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/hunk-review.spec.ts` around lines 4 - 6, Update the
Playwright imports in hunk-review.spec.ts to include the Page type alongside
expect and test, then replace the inline import('`@playwright/test`').Page
annotation in openReview with the imported Page symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/console/e2e/hunk-review.spec.ts`:
- Around line 4-6: Update the Playwright imports in hunk-review.spec.ts to
include the Page type alongside expect and test, then replace the inline
import('`@playwright/test`').Page annotation in openReview with the imported Page
symbol.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 74eef5fe-def1-49c4-adba-b81b5bcf74e6

📥 Commits

Reviewing files that changed from the base of the PR and between 0b69011 and 37ad956.

📒 Files selected for processing (10)
  • apps/console/e2e/cards.spec.ts
  • apps/console/e2e/hunk-review.spec.ts
  • apps/console/e2e/omnibar.spec.ts
  • apps/console/e2e/stub-data-api.mjs
  • apps/console/src/components/shell/IntuiShell.tsx
  • apps/console/src/components/shell/Omnibar.tsx
  • apps/console/src/lib/console-host.test.ts
  • apps/console/src/lib/console-host.ts
  • apps/console/src/lib/workspace-seed.ts
  • apps/console/src/views/registry.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/console/src/lib/console-host.test.ts
  • apps/console/e2e/stub-data-api.mjs
  • apps/console/src/views/registry.tsx
  • apps/console/src/lib/workspace-seed.ts
  • apps/console/src/lib/console-host.ts
  • apps/console/e2e/cards.spec.ts

Resolve the omnibar Ctrl/Cmd+K conflict: main #59 and this branch both landed
the browser-reliable Ask key, so the logic is identical on both sides. Keep the
fuller intent comments, take main's Prettier-conformant condition and accurate
aria-label, and take the extra Escape close-assertion in the spec.
Replace the inline import('@playwright/test').Page annotation with the imported
Page symbol (CodeRabbit nitpick).
@ecc-tools

ecc-tools Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1ebf68f15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +129 to +133
query: {
types: ['person', 'task', 'project', 'org'],
page: { limit: 400 },
live: true,
} as unknown as JsonValue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Request card relations in the Cards surface query

Against the live Rust object API, this query returns no relation data: object_ref_from_item only serializes relations requested through traverse or project.relations, while ResolvedCard renders chips exclusively from object.relations. Consequently the Cards surface has no WORKS_AT/IN_PROJECT/etc. chips in production, even though the Playwright stub masks this by returning all fixture relations regardless of the query. Add the template-required outgoing relations to this query's projection (or otherwise request them).

Useful? React with 👍 / 👎.

Comment on lines 928 to +933
ObjectAction::Create { type_ref, props } => {
let item = item_from_props(&type_ref, props)?;
let item = self.put_item(item)?;
// Incremental mention detection (K5): a new text-bearing item
// is an atom; evaluate it once, never a full rescan.
self.evaluate_mentions_for_atom(&item.id)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Evaluate existing atoms when a mention target changes

When a person or other target is created, renamed, or given an alias after text already exists, this evaluates the target itself as an atom; typical targets have no inline body, so evaluate_mentions_for_atom exits without scanning the pre-existing notes. That means candidates are created only when the note happens to be written after its target, and later target metadata changes never discover existing mentions. Re-evaluate relevant existing text atoms when a target's title or aliases change.

Useful? React with 👍 / 👎.

Comment on lines +326 to +334
let edge = EdgeRecord::new(
format!("mentioned-in:{object_id}:{atom_id}"),
object_id.as_str(),
MENTIONED_IN_EDGE,
atom_id.as_str(),
basis,
)
.with_confidence(1.0);
self.store_mut().upsert_edge(edge)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve evidence when aliases match the same atom

If one atom contains two aliases for the same object, detection creates two candidates because their IDs include the alias. Confirming each candidate writes this same mentioned-in:{object_id}:{atom_id} edge, so the second confirmation upserts it and replaces the first candidate's recorded basis. This loses confirmed provenance while the UI still represents both candidates as linked; either coalesce candidates per object/atom or retain all confirmed bases on the edge.

Useful? React with 👍 / 👎.

Comment on lines +331 to +332
const existingDocs = await this.http.query({ types: ['doc'], page: { limit: 200 } });
const slugs = new Set(existingDocs.objects.map((object) => object.properties.slug));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent duplicate fixture seeds beyond the first page

The claimed slug/path idempotency only considers the first 200 objects. In a production backend with more than 200 documents or code files, an existing seed outside that page is treated as absent and another copy is created; repeated console mounts can keep adding duplicates. Query by the seed key or paginate through all results before issuing creates.

Useful? React with 👍 / 👎.

The visual gate ran on this PR for the first time (it was not attached on the
base commit) and surfaced four intended changes plus one gap:
- cards: the cards/actions/mentions surface never had linux baselines; add
  cards-grid, card-compact-inspector, and action-sheet.
- omnibar: the expanded island hint changed with the browser-reliable Ask key
  (#59); refresh omnibar-expanded-ask.
- workspace: the nav consolidated into one leftmost stripe and docs now ride
  the wire (served by the e2e stub), so the brief reflects the stub fixture and
  the thread renders live; refresh the 1280, 1440, and reduced-motion captures.

Baselines are the ubuntu-latest actuals from the failing run artifacts, so they
match the renderer that validates them.
@ecc-tools

ecc-tools Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

const edited = editorRef.current.state.doc.toString();
setText(edited);
void host.emit({ kind: 'update', id: doc.id, patch: { markdown: edited } });
setMode('read');

P2 Badge Report failed document saves before leaving edit mode

If the object-wire update is rejected or unreachable, host.emit resolves with an error result rather than throwing, but this fire-and-forget call immediately switches the editor back to read mode with the optimistic text. The user is told nothing, and a reload loses the edit despite the new live-wire persistence behavior; await the receipt and keep edit mode or show a save failure when ok is false.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +292 to +294
private async queryLiveDomain(query: ObjectQuery, type: string): Promise<ObjectSet> {
const all = await this.http.query({ types: [type], page: { limit: 500 } });
let objects = all.objects.filter((object) => matchesPredicate(object, query.where));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Paginate live document and code queries

When the backend has more than 500 documents or code files, this fetch drops every later page before applying the client-side slug/path predicate. As a result, the Documents list omits later documents, and opening a document whose matching slug lies beyond the first 500 yields the empty state even though it exists. Follow next_cursor until exhaustion before filtering and applying the requested page.

Useful? React with 👍 / 👎.

Comment on lines +69 to +73
host.query({
types: [MENTION_CANDIDATE_TYPE],
where: { kind: 'eq', field: 'object_id', value: objectId },
page: { limit: 200 },
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retrieve all candidates for the selected mention target

For the Rust-backed object seam, object_id is stored in an item's extra map, while predicate evaluation only reads top-level serialized Item fields (crates/commonplace/src/block_view.rs:1548-1550), so this server-side filter returns the first 200 candidates globally and the client filters them afterward. Once the workspace has more than 200 candidates, a target whose candidates fall on later pages incorrectly has no mention section or an incomplete count; paginate all candidate pages or make the backend predicate address projected metadata.

Useful? React with 👍 / 👎.

Travis-Gilbert added a commit that referenced this pull request Jul 18, 2026
#62 is the canonical IA-carrying branch, so standardize its visual gate on the
repo convention: ubuntu-latest with CI-captured linux baselines (as #60 and #63
use), matching the gates job runner. ubuntu is cheaper, faster, and more
deterministic for pixel snapshots. The -linux.png references are harvested from
this run's artifact next; darwin baselines stay for local mac dev.
Travis-Gilbert added a commit that referenced this pull request Jul 18, 2026
…#62)

* fix(console): add Cmd/Ctrl+K as the browser-reliable omnibar Ask key

Cmd/Ctrl+L (handoff named choice 3, Cursor muscle memory) is a browser
accelerator reserved for the address bar, so a browser tab never delivers
the keydown to the page and the omnibar cannot capture it. It works in the
desktop shell but not in a browser. Add Cmd/Ctrl+K (the command-palette
convention browsers do deliver) as an additional Ask trigger, keeping
Cmd/Ctrl+L for the desktop target and double-Shift for Search. The field's
aria-keyshortcuts advertises all of them. e2e asserts Ctrl+K opens Ask.

* feat(console): cards, actions, mentions + stripe surface nav + live doc wire

HANDOFF-CARDS-ACTIONS-MENTIONS K1-K7:
- K1 card templates as data (person/task/generic seeds) + one card engine
  (card.full / card.compact) rendering any kind through the block contract;
  malformed or missing template degrades to generic with a note, never errors.
- K2 mounts: inspector leads with the compact card above the field table;
  cards.grid descriptor renders an ObjectQuery as virtualized faces at Twenty
  density; a Cards surface joins the seeded screens.
- K3 action sheet, three entries into one sheet (/do composer, todo-block
  arrow + Alt+Enter on Galley task items, Action verb on inspector and cards);
  staged context is always visible; auto-suggest adds removable chips; save as
  rule names IX6.
- K4 delegate wire: For me posts the pack to /api/harness/delegate and names
  the harness refusal; With me stages visible object refs above the composer.
- K5 mentions detection engine (crates/commonplace/src/mentions.rs): exact and
  normalized-exact tiers, span slices back to the original atom text,
  incremental on ingest and on object create/update, deterministic candidate
  id as the dedup and dismissal-suppression key.
- K6 mentions surface: linked/unlinked counts, passage list with the matched
  span highlighted, confirm writes the MENTIONED_IN edge with its basis on the
  edge, dismiss records the negative signal.
- K7 gates: template render-model tests, the pack-equals-chips invariant test,
  the candidate-to-confirmed-edge round trip, and cards/actions/mentions e2e
  with visual baselines.

Follow-up fixes from live review:
- Screen navigation moves out of the toolbar dropdown into the leftmost stripe
  (the stripe surfaces group): surfaces on top, a divider, then the active
  surface's tool windows, one bar in the original chrome tokens. The toolbar
  shows the active screen as a quiet breadcrumb.
- Documents and code files ride the live object wire so edits persist to the
  backend; content rides as extra properties (markdown / content) matching the
  view projection, the console filters client-side so slug/id predicates hold,
  and the backend is seeded once (idempotent by slug/path).

Verify: 40 crate tests, 29 console unit tests, 25 e2e, and the fence, register,
contrast, and motion gates all pass.

* fix(console): stop the tool-window toggle from remounting the well

Opening or closing a tool window flashed the whole content area to the
frame and back (a black frame drop), and the reflow read as broken. Cause:
the PanelGroup was keyed on the visible panel set, so every open/close
changed the key and remounted the entire well: the editor and thread tore
down and rebuilt, and the editor's delayed entrance fade re-fired, leaving
the frame showing through for a beat.

Key the group on the active surface only (a real screen switch still
remounts, which is correct), and give each Panel a stable order derived from
the full region lists so react-resizable-panels reconciles a panel appearing
or disappearing in place. The editor node now survives the toggle and the
content reflows without a teardown. Regression test asserts the editor panel
node survives an Alt+9 toggle (reconcile, not remount).

* fix(console): reconcile the stripe surfaces nav with the merged Review surface

The surface-rail navigation (replacing the toolbar layout dropdown) merged
with the Greenfield Hunk Review surface. Reconcile:
- give the review surface its own rail glyph (IconInspector);
- point the hunk-review e2e at the stripe surfaces group instead of the
  removed Layout dropdown;
- the rail now carries five surfaces, not four;
- give the injected-surface card.full assertion headroom so it does not flake
  under a cold, parallel-loaded dev server.

28 e2e, 36 unit, and the fence/register/motion/contrast gates pass on the
merged tree.

* feat(console): add register-wide coloration

* test(console): import Playwright Page type in hunk-review spec

Replace the inline import('@playwright/test').Page annotation with the imported
Page symbol (CodeRabbit nitpick).

* fix(console): run contrast gate with ts stripping

* Fix CodeMirror theme mode facet

* fix(console): use a plain range in the theme clamp note

The chroma clamp note used an en dash (0-0.04); the console constitution bans
en and em dashes in UI strings. Write it as 'the safe 0 to 0.04 range.'

* docs(console): sync the constitution to the coloration handoff

CLAUDE.md and AGENTS.md still mandated Int UI glyphs for chrome and a dark-only
'pinned register exclusively', contradicting HANDOFF-CONSOLE-COLORATION (in
force; named choices are requirements). Update the icon ledger row to the
sanctioned Noun Project policy (currentColor on the icon ladder, gate:icons,
domain tint plus file-kind dots per named choice 7 / T5), note the light
register and two-knob theme engine in the material doctrine, and add the icon
paint scan to the gate list (gate 5; visual becomes gate 6). Resolves the
CodeRabbit 'use the mandated Int UI glyph source' finding: the icons are
spec-mandated, so the governing doc was the stale artifact.

* feat(console): implement role-aware information architecture

* test(console): sync files baseline to CI renderer

* fix(console): harden information architecture interactions

* chore(console): run the visual gate on ubuntu-latest (repo convention)

#62 is the canonical IA-carrying branch, so standardize its visual gate on the
repo convention: ubuntu-latest with CI-captured linux baselines (as #60 and #63
use), matching the gates job runner. ubuntu is cheaper, faster, and more
deterministic for pixel snapshots. The -linux.png references are harvested from
this run's artifact next; darwin baselines stay for local mac dev.

* test(console): pin linux visual baselines for the ubuntu gate

Gate 6 now runs on ubuntu-latest, so every surface needs a -linux.png
reference. Harvested the 14 actuals from the ubuntu CI run (appearance light,
cards, information architecture chat/context-graph/files/stripe, hunk review,
search field) and pinned them. Darwin baselines stay for local mac dev.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@Travis-Gilbert

Copy link
Copy Markdown
Owner Author

Superseded by #62, now merged to main. #62 integrated this PR's work and carries it into main: cards/actions/mentions, the live doc/code wire (queryLiveDomain is on main), and the tool-window remount fix (the activeSurfaceId surface grouping is on main). During the information-architecture integration the leftmost-stripe nav was intentionally reconciled back to the dropdown switcher. Closing as subsumed; the branch is preserved, so any unique infra tweak (.railwayignore, playwright.config.ts) can still be cherry-picked if wanted.

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.

3 participants