Skip to content

feat: add Exhibit Stage fixture gallery - #121

Merged
Coldaine merged 2 commits into
mainfrom
feat/v1-exhibits
Jul 27, 2026
Merged

feat: add Exhibit Stage fixture gallery#121
Coldaine merged 2 commits into
mainfrom
feat/v1-exhibits

Conversation

@Coldaine

@Coldaine Coldaine commented Jul 27, 2026

Copy link
Copy Markdown
Owner

User description

Summary

  • Recover Exhibit Stage components from feat/exhibit-components into web/components/exhibits/
  • Additive ?mode=exhibits entry for fixture gallery; default remains AgentVisualizer
  • Restore docs/ideas/repoviz/exhibit-prototype.jsx from the v0 pin as look/feel reference

Honest status

Test plan

  • npm run build
  • Open http://127.0.0.1:5173/?mode=exhibits (or Electron with that query) — fixture exhibits render
  • Default URL still shows AgentVisualizer

CodeAnt-AI Description

Add an interactive Exhibit Stage for exploring curated agent-session stories

What Changed

  • Added an optional ?mode=exhibits gallery while keeping the live visualizer as the default view
  • Added seven exhibit views covering relationships, activity stories, walkthroughs, concerns, momentum, session events, and code heat
  • Gallery items show narratives, relevance, status, commentary, and retired artifacts in an archive shelf
  • Exhibits rotate automatically, pause while hovered or interacted with, and support manual navigation
  • Added hover and keyboard focus details, timeline scrubbing, reduced-motion support, and a placeholder for the live graph

Impact

✅ Interactive agent-session gallery
✅ Clearer progress, risks, and unfinished work
✅ Live visualizer remains unchanged by default

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Copilot AI review requested due to automatic review settings July 27, 2026 08:48
@codeant-ai

codeant-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 36a6421 Jul 27, 2026 · 08:48 08:54

@codeant-ai

codeant-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 38 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0cb4fcc7-3f2a-4bcf-9e69-a5ad9eefecc4

📥 Commits

Reviewing files that changed from the base of the PR and between 0b35343 and 1104423.

📒 Files selected for processing (17)
  • docs/ideas/repoviz/exhibit-prototype.jsx
  • tests/exhibit-contract.test.ts
  • web/app-entry.tsx
  • web/components/exhibits/ActivityNarrative.tsx
  • web/components/exhibits/ConcernSnapshot.tsx
  • web/components/exhibits/ExhibitGalleryApp.tsx
  • web/components/exhibits/ExhibitStage.tsx
  • web/components/exhibits/Momentum.tsx
  • web/components/exhibits/RelationshipDag.tsx
  • web/components/exhibits/Seismograph.tsx
  • web/components/exhibits/ThermalMap.tsx
  • web/components/exhibits/Walkthrough.tsx
  • web/components/exhibits/exhibits.css
  • web/components/exhibits/fixture-gallery.ts
  • web/components/exhibits/index.ts
  • web/components/exhibits/primitives.tsx
  • web/components/exhibits/types.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v1-exhibits

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.

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Jul 27, 2026

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

The fixture stage is missing one required exhibit type.

* set by relevance for rotation; retired artifacts fall to the archive shelf.
*/
export const fixtureGallery: ExhibitArtifact[] = [
activityNarrative,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ThermalMap is implemented but no thermal_map artifact is declared or added to this gallery. As a result, ?mode=exhibits can never render that view, even though the v1 acceptance criteria require all seven authored exhibits plus live_graph to render from typed contracts. Please add a thermal-map fixture here (and include it in the gallery) so this fixture-only stage exercises the full vocabulary.

Comment on lines +104 to +108
const active = useMemo(
() => artifacts.filter((a) => a.status !== 'retired').sort((a, b) => b.relevance - a.relevance),
[artifacts]
);
const retired = useMemo(() => artifacts.filter((a) => a.status === 'retired'), [artifacts]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The selected exhibit is stored only as a positional index, so when artifacts refreshes or relevance values reorder active, the same index can point to a different artifact and silently change the visible exhibit. Track the selected artifact ID and derive its current index after rebuilding the sorted list, falling back only when that ID no longer exists. [stale reference]

Severity Level: Major ⚠️
- ⚠️ Refreshed curator exhibits can change selection unexpectedly.
- ⚠️ Commentary and renderer may no longer match user choice.
- ⚠️ Active exhibit state is unstable during relevance updates.
Steps of Reproduction ✅
1. The `?mode=exhibits` entry in `web/app-entry.tsx:10-12` mounts `ExhibitGalleryApp`,
which passes `fixtureGallery` to `ExhibitStage` at
`web/components/exhibits/ExhibitGalleryApp.tsx:26-38`.

2. `ExhibitStage` builds `active` by filtering and relevance-sorting the supplied
artifacts at `web/components/exhibits/ExhibitStage.tsx:104-108`, while storing only the
selected position in `index` at lines 110-115.

3. Render the stage with multiple artifacts, select a non-first item through `goTo()` at
`web/components/exhibits/ExhibitStage.tsx:139-142`, then refresh the supplied artifact
array with changed relevance values so its sorted order changes.

4. React preserves `index`, but `active[safeIndex]` at
`web/components/exhibits/ExhibitStage.tsx:115-116` now refers to a different artifact; the
displayed title, commentary, renderer, and active rail item silently switch without the
user selecting another exhibit. The current fixture caller is static, but the component's
`artifacts` prop is explicitly designed for refreshed artifact data.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** web/components/exhibits/ExhibitStage.tsx
**Line:** 104:108
**Comment:**
	*Stale Reference: The selected exhibit is stored only as a positional index, so when `artifacts` refreshes or relevance values reorder `active`, the same index can point to a different artifact and silently change the visible exhibit. Track the selected artifact ID and derive its current index after rebuilding the sorted list, falling back only when that ID no longer exists.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +316 to +325
export const fixtureGallery: ExhibitArtifact[] = [
activityNarrative,
relationshipDag,
walkthrough,
concernSnapshot,
seismograph,
momentum,
liveGraph,
retiredEarlyGuess,
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The fixture gallery omits the thermal_map artifact even though this PR adds the Thermal Map renderer, includes it in the supported exhibit vocabulary, and describes the gallery as the full exhibit floor. As a result, the new exhibit can never be reached through the advertised fixture gallery; add a thermal-map fixture artifact to the array. [incomplete implementation]

Severity Level: Major ⚠️
- ❌ Fixture gallery cannot demonstrate Thermal Map.
- ⚠️ `?mode=exhibits` omits a supported exhibit type.
- ⚠️ ThermalMap renderer is unreachable through fixture navigation.
Steps of Reproduction ✅
1. Open the documented fixture route `?mode=exhibits`; `web/app-entry.tsx:7-12` mounts
`ExhibitGalleryApp`, and `web/components/exhibits/ExhibitGalleryApp.tsx:38` passes
`fixtureGallery` into `ExhibitStage`.

2. `fixtureGallery` contains activity narrative, relationship DAG, walkthrough, concern
snapshot, seismograph, momentum, live graph, and one retired artifact at
`web/components/exhibits/fixture-gallery.ts:316-325`; it contains no artifact with
`exhibitType: 'thermal_map'`.

3. `ExhibitStage` filters the active gallery and renders only those artifacts at
`web/components/exhibits/ExhibitStage.tsx:104-108` and `154-175`, so the fixture route has
no rail item or rotation entry for thermal maps.

4. Although `thermal_map` is part of `ExhibitPayloadMap` and `AUTHORED_EXHIBIT_TYPES` at
`web/components/exhibits/types.ts:165-184` and `253-262`, and `ActiveExhibit` dispatches
it to `ThermalMap` at `web/components/exhibits/ExhibitStage.tsx:79`, that renderer is
unreachable through the advertised fixture gallery until a thermal-map artifact is added.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** web/components/exhibits/fixture-gallery.ts
**Line:** 316:325
**Comment:**
	*Incomplete Implementation: The fixture gallery omits the `thermal_map` artifact even though this PR adds the Thermal Map renderer, includes it in the supported exhibit vocabulary, and describes the gallery as the full exhibit floor. As a result, the new exhibit can never be reached through the advertised fixture gallery; add a thermal-map fixture artifact to the array.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +102 to +107
<div className="relative h-[720px] overflow-hidden rounded-[30px] border border-white/10 bg-[#050914] shadow-[0_0_0_1px_rgba(255,255,255,0.02),0_30px_80px_rgba(0,0,0,0.65)]">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_32%),radial-gradient(circle_at_75%_10%,rgba(192,132,252,0.14),transparent_28%),radial-gradient(circle_at_50%_100%,rgba(52,211,153,0.10),transparent_24%)]" />
<div className="absolute inset-0 opacity-[0.08] [background-image:linear-gradient(rgba(255,255,255,0.9)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.9)_1px,transparent_1px)] [background-size:60px_60px]" />
<div className="absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0.02),transparent_12%,transparent_88%,rgba(255,255,255,0.02))]" />
{children}
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The fixed 720px frame clips exhibit content at shorter viewport heights because overflow is hidden. The momentum view contains stacked panels whose content can exceed this height, and the fixed-width cards also overflow on narrow screens, so users can lose controls and narrative content instead of being able to scroll or see a responsive layout. Use a responsive/min-height frame and allow the exhibit content to scroll or reflow at small viewport sizes. [css layout issue]

Severity Level: Major ⚠️
- ⚠️ Short-window prototype users can lose lower content.
- ⚠️ Narrow-window users can lose controls or card text.
- ⚠️ The restored prototype is not responsive despite viewport use.
Steps of Reproduction ✅
1. Render `RepoExhibitVisualizationPrototype` from
`docs/ideas/repoviz/exhibit-prototype.jsx:739` in a viewport shorter than 720 CSS pixels
or narrower than the fixed card layout requires.

2. Select `MomentumView` through `ViewStage` at
`docs/ideas/repoviz/exhibit-prototype.jsx:731-736`; its panels are positioned inside the
`Frame` beginning at line `631`.

3. The `Frame` at `docs/ideas/repoviz/exhibit-prototype.jsx:102` always has `h-[720px]`
and `overflow-hidden`, so content extending beyond that rectangle cannot increase the
frame height or be scrolled.

4. On narrow widths, Momentum's fixed internal dimensions, including the `240px` gauge at
`docs/ideas/repoviz/exhibit-prototype.jsx:648` and fixed-width cards in the other views,
can extend beyond the available frame width; the overflow rule clips the excess controls
or narrative instead of allowing reflow or scrolling.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** docs/ideas/repoviz/exhibit-prototype.jsx
**Line:** 102:107
**Comment:**
	*Css Layout Issue: The fixed 720px frame clips exhibit content at shorter viewport heights because overflow is hidden. The momentum view contains stacked panels whose content can exceed this height, and the fixed-width cards also overflow on narrow screens, so users can lose controls and narrative content instead of being able to scroll or see a responsive layout. Use a responsive/min-height frame and allow the exhibit content to scroll or reflow at small viewport sizes.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +815 to +816
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The hover handlers overwrite the user's manual pause state: moving the pointer into the exhibit list or stage forces paused to true, and leaving forces it to false. As a result, a user who manually pauses cycling can have it resume simply by hovering and leaving these regions, and clicking the pause button while the pointer is over the list can immediately toggle the hover-forced state back to active. Keep manual pause and hover pause as separate state values, or only apply hover pausing when the user has not manually paused. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ Prototype cycling can resume against user intent.
- ⚠️ Manual pause behavior is inconsistent across hover regions.
- ⚠️ Current `?mode=exhibits` uses a separate `ExhibitStage` implementation.
Steps of Reproduction ✅
1. The current browser entry at `web/app-entry.tsx:10-12` mounts `ExhibitGalleryApp` for
`?mode=exhibits`; it does not import or render `RepoExhibitVisualizationPrototype` from
`docs/ideas/repoviz/exhibit-prototype.jsx:739`, so this behavior is limited to the
restored prototype when that file is rendered.

2. Render `RepoExhibitVisualizationPrototype` and click the `Pause cycling` button
implemented at `docs/ideas/repoviz/exhibit-prototype.jsx:794-799`; this sets the shared
`paused` state to `true` and stops the interval at lines `746-755`.

3. Move the pointer over any exhibit-list button at
`docs/ideas/repoviz/exhibit-prototype.jsx:813-817`; its `onMouseEnter` handler sets the
same state to `true`, masking the manual state but not distinguishing its source.

4. Move the pointer away from the button; `onMouseLeave` at
`docs/ideas/repoviz/exhibit-prototype.jsx:816` sets `paused` to `false`, restarting the
interval even though the user previously selected manual pause. The stage container has
the same overriding handlers at lines `852-855`.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** docs/ideas/repoviz/exhibit-prototype.jsx
**Line:** 815:816
**Comment:**
	*Incorrect Condition Logic: The hover handlers overwrite the user's manual pause state: moving the pointer into the exhibit list or stage forces `paused` to true, and leaving forces it to false. As a result, a user who manually pauses cycling can have it resume simply by hovering and leaving these regions, and clicking the pause button while the pointer is over the list can immediately toggle the hover-forced state back to active. Keep manual pause and hover pause as separate state values, or only apply hover pausing when the user has not manually paused.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

export default function ActivityNarrative({ payload }: ActivityNarrativeProps) {
const reduce = useReducedMotion();
const { beats, threads } = payload;
const [active, setActive] = useState(beats.length - 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The selected index is initialized from the initial beat count but is never reconciled when beats changes. An empty payload initializes active to -1, and a refresh that removes beats can leave active above the new slider maximum, producing an invalid controlled range input and no valid selected beat. Clamp or reset active whenever the beat list changes. [stale reference]

Severity Level: Major ⚠️
- ❌ Refreshed activity exhibits can show invalid timeline selection.
- ⚠️ Empty payloads render an out-of-range controlled slider.
- ⚠️ Automatic beat cycling can reference stale selection state.
Steps of Reproduction ✅
1. Open the fixture gallery through `ExhibitGalleryApp` at
`web/components/exhibits/ExhibitGalleryApp.tsx:26-38`; `ExhibitStage` selects the activity
artifact through `web/components/exhibits/ExhibitStage.tsx:73-74`.

2. The fixture currently supplies seven beats at
`web/components/exhibits/fixture-gallery.ts:38-88`, so `ActivityNarrative` initializes
`active` to `6` at `web/components/exhibits/ActivityNarrative.tsx:20`.

3. Refresh the same mounted activity artifact with an empty `payload.beats` array, or with
fewer beats than the previous payload. The state initializer does not rerun, leaving
`active` at `6` (or `-1` for an initially empty payload).

4. The controlled range input at `web/components/exhibits/ActivityNarrative.tsx:104-110`
then receives `value={active}` while its maximum is `Math.max(0, beats.length - 1)`,
producing an out-of-range controlled slider and no valid selected beat. Add an effect that
clamps or resets `active` when `beats.length` changes.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** web/components/exhibits/ActivityNarrative.tsx
**Line:** 20:20
**Comment:**
	*Stale Reference: The selected index is initialized from the initial beat count but is never reconciled when `beats` changes. An empty payload initializes `active` to `-1`, and a refresh that removes beats can leave `active` above the new slider maximum, producing an invalid controlled range input and no valid selected beat. Clamp or reset `active` whenever the beat list changes.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

const reduce = useReducedMotion();
const { concerns, flows } = payload;
const byId = useMemo(() => Object.fromEntries(concerns.map((c) => [c.id, c])), [concerns]);
const [selected, setSelected] = useState(concerns[0]?.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.

Suggestion: The selected concern ID is initialized only once and is not synchronized with refreshed concerns. If the selected concern is removed or replaced while the artifact remains mounted, current falls back to the first concern while no concern receives the selected styling, so the focus card and highlighted box describe different concerns. Reconcile selected against the current concern IDs when the payload changes. [stale reference]

Severity Level: Major ⚠️
- ❌ Refreshed concern maps can lose visual selection.
- ⚠️ Focus card and highlighted box can disagree.
- ⚠️ Curator updates can present stale concern context.
Steps of Reproduction ✅
1. Open the fixture gallery through `ExhibitGalleryApp` at
`web/components/exhibits/ExhibitGalleryApp.tsx:26-38`; `ExhibitStage` renders concern
snapshots through `web/components/exhibits/ExhibitStage.tsx:76`.

2. The initial concern IDs are defined at
`web/components/exhibits/fixture-gallery.ts:185-191`, and the component initializes
`selected` to the first concern ID at `web/components/exhibits/ConcernSnapshot.tsx:28`.

3. While the component remains mounted, refresh the same concern artifact with a payload
that removes that selected ID or replaces it with different IDs. The `useState`
initializer does not rerun, so `selected` still contains the removed ID.

4. At `web/components/exhibits/ConcernSnapshot.tsx:29-30`, `current` falls back to
`concerns[0]`, while the button class at
`web/components/exhibits/ConcernSnapshot.tsx:49-53` compares against the stale ID and
highlights nothing. The focus card therefore describes the first concern while no concern
is selected; reconcile `selected` whenever the concern IDs change.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** web/components/exhibits/ConcernSnapshot.tsx
**Line:** 28:28
**Comment:**
	*Stale Reference: The selected concern ID is initialized only once and is not synchronized with refreshed `concerns`. If the selected concern is removed or replaced while the artifact remains mounted, `current` falls back to the first concern while no concern receives the selected styling, so the focus card and highlighted box describe different concerns. Reconcile `selected` against the current concern IDs when the payload changes.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

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

Adds an Exhibit Stage “fixture gallery” surface to the web app, exposing a curated set of exhibit-style visualizations behind an additive ?mode=exhibits entry point while keeping the default surface as AgentVisualizer.

Changes:

  • Introduces typed exhibit artifact contracts plus a hand-authored fixture gallery to drive the stage.
  • Adds the ExhibitStage UI (rail + rotating main frame) and ports/implements multiple exhibit renderers + shared primitives/CSS.
  • Adds a ?mode=exhibits switch in web/app-entry.tsx and restores a prototype reference doc.

Reviewed changes

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

Show a summary per file
File Description
web/components/exhibits/Walkthrough.tsx Walkthrough exhibit renderer (central narrative + satellites/files).
web/components/exhibits/types.ts Exhibit artifact envelope + discriminated payload types and helpers.
web/components/exhibits/ThermalMap.tsx Thermal map exhibit renderer with squarified treemap layout.
web/components/exhibits/Seismograph.tsx Seismograph exhibit renderer (time-axis trace + annotations).
web/components/exhibits/RelationshipDag.tsx Relationship DAG exhibit renderer (nodes/edges + focus card).
web/components/exhibits/primitives.tsx Shared exhibit primitives (Frame/Chip/Node) + SVG helpers.
web/components/exhibits/Momentum.tsx Momentum exhibit renderer (gauge + stats + next items).
web/components/exhibits/index.ts Barrel exports for the exhibits surface.
web/components/exhibits/fixture-gallery.ts Hand-authored fixture ExhibitArtifact[] gallery content.
web/components/exhibits/ExhibitStage.tsx The stage container (rail, rotation, active exhibit swapping).
web/components/exhibits/exhibits.css CSS for the exhibit stage + all exhibit visuals.
web/components/exhibits/ExhibitGalleryApp.tsx Standalone fixture-only app wrapper for the stage.
web/components/exhibits/ConcernSnapshot.tsx Concern snapshot exhibit renderer (heat + flows + focus card).
web/components/exhibits/ActivityNarrative.tsx Activity narrative exhibit renderer (timeline foldout + scrub).
web/app-entry.tsx Adds ?mode=exhibits route switch; default remains AgentVisualizer.
docs/ideas/repoviz/exhibit-prototype.jsx Restored visual prototype reference document.

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

export default function ActivityNarrative({ payload }: ActivityNarrativeProps) {
const reduce = useReducedMotion();
const { beats, threads } = payload;
const [active, setActive] = useState(beats.length - 1);
Comment on lines +2 to +5
* Fixture gallery — a hand-authored exhibition of the REAL Codex homelab
* coordinator session (see
* `tests/fixtures/transcripts/codex/rollout-2026-07-23-homelab-coordinator.ground-truth.md`).
*
Comment on lines +15 to +19
} from "lucide-react";

type ViewId = "dag" | "timeline" | "walkthrough" | "architecture" | "momentum";

type ViewDef = {
*/
export { default as ExhibitStage } from './ExhibitStage';
export type { ExhibitStageProps } from './ExhibitStage'
export { default as fixtureGallery } from './fixture-gallery'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The public barrel export includes fixtureGallery, which contains details from a real homelab coordinator session, including the exposed FORGEJO_INTERNAL_TOKEN incident and rollout history. Because the gallery is reachable through the production client bundle, any visitor who opens the exhibits mode can inspect these operational details. Replace this with sanitized synthetic fixture data or keep real session fixtures out of the client bundle. [security]

Severity Level: Major ⚠️
- ⚠️ Exhibit mode discloses real homelab operational history.
- ⚠️ Client bundles expose token names and remediation details.
- ⚠️ Database, deployment, and credential-state metadata becomes public.
Steps of Reproduction ✅
1. Build the web client using the repository's `npm run build:web` script from
`package.json:11`; Vite statically follows the barrel export at
`web/components/exhibits/index.ts:11` and includes `fixture-gallery.ts` in the client
bundle.

2. Open the documented gallery entry point with `?mode=exhibits`;
`web/app-entry.tsx:10-12` and `web/electron-entry.tsx:11-15` select `ExhibitGalleryApp`
for that query.

3. `ExhibitGalleryApp` imports and passes `fixtureGallery` to `ExhibitStage` at
`web/components/exhibits/ExhibitGalleryApp.tsx:6-8,26-38`, making the fixture data
available to every gallery visitor.

4. Inspect the rendered exhibit or downloaded JavaScript bundle and observe real-session
operational details from
`web/components/exhibits/fixture-gallery.ts:2-13,69-70,112,259-261`, including the named
`FORGEJO_INTERNAL_TOKEN` incident, database scope, deployment status, GHCR failure, and
deferred rotation. The token value itself is not present, but these details should still
be replaced with synthetic data or excluded from the client bundle.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** web/components/exhibits/index.ts
**Line:** 11:11
**Comment:**
	*Security: The public barrel export includes `fixtureGallery`, which contains details from a real homelab coordinator session, including the exposed `FORGEJO_INTERNAL_TOKEN` incident and rollout history. Because the gallery is reachable through the production client bundle, any visitor who opens the exhibits mode can inspect these operational details. Replace this with sanitized synthetic fixture data or keep real session fixtures out of the client bundle.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Coldaine added a commit that referenced this pull request Jul 27, 2026
- Add vitest + root test infrastructure (vitest.config.ts, vitest.config.build.ts).
  npm test runs the fast IPC contract suite; npm run test:build runs the slow
  build-smoke suite separately.
- tests/ipc-contract.test.ts: pin IPC channel names, assert uniqueness, assert
  avc: namespace (not shadow:/vscode:), assert runtime-frozen. The freeze test
  caught a real gap: IPC was declared `as const` (compile-time only) but not
  Object.freeze'd at runtime — a rename would silently succeed in JS.
- electron/ipc-channels.ts: wrap IPC in Object.freeze() so the contract test
  passes and the wire protocol is runtime-immutable.
- tests/build-smoke.test.ts: run `npm run build` and assert dist-web/index.html +
  dist-electron/main.js exist with non-trivial size. Catches the stacked-PR
  dependency leak below.
- web/electron-entry.tsx: remove the ExhibitGalleryApp import and ?mode=exhibits
  switch. The substrate PR (PR #119) shipped an import of ./components/exhibits
  which only resolves in PR #121 — the substrate PR did not build independently.
  The exhibits entry switch belongs in PR #121, not the substrate PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Coldaine
Coldaine force-pushed the feat/v1-ingestion branch from 586f9b3 to f81144c Compare July 27, 2026 11:03
@kilo-code-bot

kilo-code-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No New Issues Found | Recommendation: Review existing comments before merge

All issues identified in this PR have already been flagged in existing review comments. The pre-existing comments cover valid concerns that should be addressed:

  • fixture-gallery.ts - Dead reference to non-existent ground-truth fixture file; thermal_map exhibit type is implemented but has no artifact in the fixture gallery
  • ActivityNarrative.tsx - active initializes to -1 when beats array is empty; index not synchronized when beats refresh
  • ConcernSnapshot.tsx - Selected concern ID not synchronized when payload refreshes
  • index.ts - fixtureGallery export exposes real homelab operational details in client bundle
  • exhibit-prototype.jsx - TypeScript type syntax in .jsx file; hover handlers override manual pause state
  • ExhibitStage.tsx - Selection stored only as position index without synchronization

Please address the existing comments before merging.

Note on doctrine: Per docs/plans/roadmap.md and docs/architecture.md, this repo follows the "copied substrate" doctrine where agent-flow is copied as the renderer substrate. Documentation files in docs/ideas/repoviz/ serve as reference/prototype material, not production code.

Files Reviewed (16 files)
  • docs/ideas/repoviz/exhibit-prototype.jsx
  • tests/exhibit-contract.test.ts
  • web/app-entry.tsx
  • web/components/exhibits/ActivityNarrative.tsx
  • web/components/exhibits/ConcernSnapshot.tsx
  • web/components/exhibits/ExhibitGalleryApp.tsx
  • web/components/exhibits/ExhibitStage.tsx
  • web/components/exhibits/Momentum.tsx
  • web/components/exhibits/RelationshipDag.tsx
  • web/components/exhibits/Seismograph.tsx
  • web/components/exhibits/ThermalMap.tsx
  • web/components/exhibits/Walkthrough.tsx
  • web/components/exhibits/exhibits.css
  • web/components/exhibits/fixture-gallery.ts
  • web/components/exhibits/index.ts
  • web/components/exhibits/primitives.tsx
  • web/components/exhibits/types.ts
Previous Review Summary (commit 7e3ab0a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7e3ab0a)

Status: No New Issues Found | Recommendation: Review existing comments before merge

All issues identified in this PR have already been flagged in existing review comments. The pre-existing comments cover valid concerns that should be addressed:

  • fixture-gallery.ts - Dead reference to non-existent ground-truth fixture file; thermal_map exhibit type is implemented but has no artifact in the fixture gallery
  • ActivityNarrative.tsx - active initializes to -1 when beats array is empty; index not synchronized when beats refresh
  • ConcernSnapshot.tsx - Selected concern ID not synchronized when payload refreshes
  • index.ts - fixtureGallery export exposes real homelab operational details in client bundle
  • exhibit-prototype.jsx - TypeScript type syntax in .jsx file; hover handlers override manual pause state
  • ExhibitStage.tsx - Selection stored only as position index without synchronization

Please address the existing comments before merging.

Note on doctrine: Per docs/plans/roadmap.md and docs/architecture.md, this repo follows the "copied substrate" doctrine where agent-flow is copied as the renderer substrate. Documentation files in docs/ideas/repoviz/ serve as reference/prototype material, not production code.

Files Reviewed (16 files)
  • docs/ideas/repoviz/exhibit-prototype.jsx
  • tests/exhibit-contract.test.ts
  • web/app-entry.tsx
  • web/components/exhibits/ActivityNarrative.tsx
  • web/components/exhibits/ConcernSnapshot.tsx
  • web/components/exhibits/ExhibitGalleryApp.tsx
  • web/components/exhibits/ExhibitStage.tsx
  • web/components/exhibits/Momentum.tsx
  • web/components/exhibits/RelationshipDag.tsx
  • web/components/exhibits/Seismograph.tsx
  • web/components/exhibits/ThermalMap.tsx
  • web/components/exhibits/Walkthrough.tsx
  • web/components/exhibits/exhibits.css
  • web/components/exhibits/fixture-gallery.ts
  • web/components/exhibits/index.ts
  • web/components/exhibits/primitives.tsx
  • web/components/exhibits/types.ts

Reviewed by ling-3.0-flash-free · Input: 86.4K · Output: 4.3K · Cached: 112.8K

Coldaine added a commit that referenced this pull request Jul 27, 2026
* feat: codex replay corpus + ingestion/replay design

Add a real, sanitized Codex CLI rollout (3,677 lines, 4.5h homelab
database-recovery coordinator session) as the deterministic replay
corpus, with human-verified ground truth as assertable expectations,
and the design for the Codex harness driver + deterministic replay
runner (docs/plans/plan-codex-replay.md).

Fixture provenance and sanitization (17 secrets -> REDACTED_*) are
documented in the ground-truth file; both files are frozen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU

* docs: capture v1 curator rebuild requirements before pin

Record the nuke-and-copy-agent-flow plan (req-v1-curator, visual substrate
inventory) and point roadmap M2/M3 at them so the v0 snapshot preserves the
restart rationale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: replace v0 codebase with copied agent-flow substrate

Nuke the classifier-era src/tests/docs and copy agent-flow web/ +
extension ingestion (minus VS Code glue) as the v1 renderer substrate.
Apache-2.0 LICENSE + NOTICE; fresh governing docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: scaffold Electron shell with IPC bridge for agent-flow renderer

Add flat root package.json, main/preload, and an Electron Vite SPA entry that
reuses vscode-bridge over contextBridge IPC. Document copy-verify findings
(prefer app-style SPA over webview IIFE; SSE host is reference-only).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: make pre-push tests pass on substrate PR

Substrate slice has no host/ingestion package yet; stub test scripts so
stacked PR pushes work with the existing pre-push hook.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* test: harden substrate PR with build-smoke + IPC contract tests

- Add vitest + root test infrastructure (vitest.config.ts, vitest.config.build.ts).
  npm test runs the fast IPC contract suite; npm run test:build runs the slow
  build-smoke suite separately.
- tests/ipc-contract.test.ts: pin IPC channel names, assert uniqueness, assert
  avc: namespace (not shadow:/vscode:), assert runtime-frozen. The freeze test
  caught a real gap: IPC was declared `as const` (compile-time only) but not
  Object.freeze'd at runtime — a rename would silently succeed in JS.
- electron/ipc-channels.ts: wrap IPC in Object.freeze() so the contract test
  passes and the wire protocol is runtime-immutable.
- tests/build-smoke.test.ts: run `npm run build` and assert dist-web/index.html +
  dist-electron/main.js exist with non-trivial size. Catches the stacked-PR
  dependency leak below.
- web/electron-entry.tsx: remove the ExhibitGalleryApp import and ?mode=exhibits
  switch. The substrate PR (PR #119) shipped an import of ./components/exhibits
  which only resolves in PR #121 — the substrate PR did not build independently.
  The exhibits entry switch belongs in PR #121, not the substrate PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Coldaine
Coldaine force-pushed the feat/v1-ingestion branch from f81144c to a7ba849 Compare July 27, 2026 11:59
Base automatically changed from feat/v1-ingestion to main July 27, 2026 12:00
Coldaine and others added 2 commits July 27, 2026 07:01
Recover typed exhibit components, fixture gallery, and stage CSS under
web/components/exhibits; restore exhibit-prototype.jsx from the v0 pin.
Additive ?mode=exhibits entry leaves AgentVisualizer as the default surface.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add tests/exhibit-contract.test.ts: a pure (non-DOM) contract test for the
exhibit vocabulary and fixture gallery. Asserts the typed contracts from
docs/plans/req-v1-curator.md §6 hold:

- AUTHORED_EXHIBIT_TYPES is exactly the v1 seven (no live_graph)
- Every fixture artifact is a valid ExhibitArtifact (id, title, narrative,
  relevance in [0,1], decayClass, status, createdAtEvent)
- narrative is MANDATORY and non-trivial (>20 chars — not a placeholder)
- exhibitType is in the v1 vocabulary (7 authored + live_graph)
- ids are unique; relevance values are spread; >=5 distinct types covered
- Type guards (isExhibitOfType, isRetired) narrow correctly

This is the test the prior suite was missing: the exhibit type contracts and
fixture gallery were unverified. A DOM mount test belongs in a later PR once
the live-exhibits hook (PR #123) and a jsdom + testing-library setup land.

The root vitest config arrives via rebase from the substrate PR (#119);
the test runs via `npx vitest run tests/exhibit-contract.test.ts` in the
meantime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Coldaine
Coldaine merged commit 74c6957 into main Jul 27, 2026
3 of 4 checks passed
@Coldaine
Coldaine deleted the feat/v1-exhibits branch July 27, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants