Replace user-visible "company" display strings with "workspace"
in the workspace rail, agent detail, and workspace settings page.
Code identifiers, API paths, and types remain on the upstream
"company" vocabulary per PROJECT.md sync constraint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 9 used `h-[calc(100vh-320px)] min-h-[320px]` on the thread
wrapper because `@tanstack/react-virtual` needs an explicit height
for its virtual scroll root. The calc was brittle — any change to
TopStrip or input bar sizing broke the math and let the thread
overflow or collapse below 320px.
Phase 16a replaces the calc with a pure flex chain:
- PersonalAssistant forks its root column: when a conversation is
active it renders a `flex-1 min-h-0` child that centers the
message list at max-w-[760px] and delegates scroll to the list's
own internal virtual scroller. When no conversation is active the
old `overflow-auto` wrapper remains so the greeting and empty
state still scroll.
- ChatMessageList's root becomes `flex min-h-0 flex-1 flex-col`,
and its inner scroll div becomes `flex-1 min-h-0 overflow-auto`.
The virtualizer now measures the actual flex-derived height,
which tracks input-bar size changes automatically.
Tests: src/components/frame/ + src/components/assistant/ +
ChatMessageList.test — 109 passed (6 todo).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IconRail (desktop) and MobileTabBar (mobile) each carried hand-
synced copies of the 4-destination nav config — labels, href
builders, isActive regex predicates, and Lucide icons. Phase 15
explicitly deferred the DRY to Phase 16 cleanup.
Phase 16a extracts the shared config into `frame/destinations.ts`
and replaces both consumers' inline definitions with a single
import. The Projects umbrella regex list (issues, agents, routines,
goals, approvals, costs, activity, inbox, execution-workspaces)
lives in the shared module so both surfaces stay in lockstep.
Net line change for the two consumers:
- IconRail.tsx: -67 lines (inline config)
- MobileTabBar.tsx: -63 lines (inline config)
- +82 lines (new destinations.ts with shared types + regex list)
Test sweep: src/components/frame/ — 53 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 16a removes the legacy chrome surfaces that the Phase 8
frame skeleton rewrite replaced:
- ChatPanel.tsx — 380px desktop slide-in drawer; superseded by the
full-bleed /assistant page and HistorySheet slide-over.
- ChatPanelContext.tsx — drawer open/close + conversation-id
state; migrated to prop drilling in the preceding commit.
- MobileChatView.tsx — only consumed by ChatPanel, orphaned with it.
- Sidebar.tsx — old left sidebar frame; replaced by IconRail.
- BreadcrumbBar.tsx — old top breadcrumb strip; replaced by
TopStrip + ModeBreadcrumb.
- PropertiesPanel.tsx — orphan placeholder from the pre-Nexus
three-pane layout.
- VoiceMicButton.tsx + useVadRecorder.ts — Phase 14 orphaned these
when voice routing moved into VoiceContext + GlobalMicButton.
main.tsx drops the now-unused ChatPanelProvider wrapper. Grep for
each symbol across ui/src returns zero surviving consumers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 9 flagged that ChatMessageList, ChatConversationList, and
PersonalAssistant all read activeConversationId / scrollToMessageId
from the legacy ChatPanelContext, blocking deletion of the old
desktop chat drawer. Phase 16a migrates those reads off context
using prop-drilling through HistorySheet so PersonalAssistant owns
a local useState for the active conversation id.
- ChatMessageList accepts optional scrollToMessageId/onScrollComplete
props in place of useChatPanel().
- ChatConversationList accepts activeConversationId + onSelectConversation
so HistorySheet can pass them through.
- HistorySheet forwards the new props to its embedded list.
- PersonalAssistant owns activeConversationId as local state and
wires HistorySheet to it.
- NexusOnboardingWizard drops its setChatOpen(true) call — the
Assistant page is the chat surface now, no drawer to toggle.
Leaves ChatPanel.tsx and MobileChatView.tsx as the only remaining
useChatPanel consumers; both are dead chrome slated for deletion
in the next commit of this phase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three coordinated fixes after phases 12, 13, 14, 15 all landed in
parallel on nexus/design-system-migration:
1. TopStrip.test.tsx provider stubs for phase 14 contexts.
Phase 14 wired CmdKButton to a new CommandPaletteContext via the
useCommandPalette() hook. TopStrip renders CmdKButton internally,
so its existing tests (written in phase 8 before the context
existed) throw "useCommandPalette must be used within a
CommandPaletteProvider" on every case. Same issue on GlobalMicButton
which now reads from VoiceContext.
Fix: add module-scope vi.mock() stubs for both contexts returning
minimal shapes, matching the existing CompanyContext mock pattern
already in the file. No test semantics change - the TopStrip is
still rendered in isolation, still verified to contain the three
children (ModeBreadcrumb, CmdKButton, GlobalMicButton) and the
header landmark. All 4 TopStrip tests pass again.
Phase 15's report flagged this breakage explicitly and confirmed
via git stash that it was pre-existing on phase 14's HEAD, not
introduced by phase 15.
2. PromoteTransition mobile variant.
Phase 15 deferred the mobile variant of the promote-to-project
transition because PromoteTransition.tsx did not yet exist when
phase 15 started (phase 12 created it mid-dispatch). The defer
was correct per explicit instructions.
Fix: add a CSS media query inside the existing scoped <style>
block in PromoteTransition.tsx:
@media (max-width: 767px) {
.nx-promote-ribbon[data-pstate="prompting"],
.nx-promote-ribbon[data-pstate="creating"] {
max-height: 0;
box-shadow: none;
overflow: hidden;
}
.nx-promote-label[data-pstate="prompting"],
.nx-promote-label[data-pstate="creating"] {
display: none;
}
}
On mobile the brainstormer completely covers the chat thread
instead of sharing a 30/70 split, per spec section 9.1. The panel
itself already takes the remaining viewport height via flex, so
once the ribbon collapses to zero the brainstormer naturally
fills the whole area.
Pure CSS - no JS media query, no useMediaQuery call, no test
changes needed. Single 768px breakpoint matches the rest of the
Nexus frame.
3. Layout.tsx onSearch now calls useCommandPalette().setOpen(true)
directly instead of dispatching a synthetic Cmd+K keydown.
Phase 14 installed a real command palette context with a provider
mounted in main.tsx, but left Layout.tsx's onSearch callback using
the old synthetic keydown shim because Layout is phase 15-owned
(parallel dispatch rules). The synthetic dispatch worked end-to-end
because the provider's global keydown listener catches it, but it's
a code smell: a Layout-level callback generating a synthetic event
for a listener the Layout also owns.
Fix: import useCommandPalette, hold a commandPalette ref in the
Layout body, and replace the synthetic keydown dispatch with
commandPalette.setOpen(true). Drop the Phase 8 shim comment; leave
a single-line Phase 14 explanation.
All 294 tests pass across 37 test files (frame, assistant, studio,
projects, settings, Voice/CommandPalette contexts, home-status and
gate-indicator and promote-to-project hooks, StudioWorkshopDetail
page). Typecheck clean across every wave 1-3A file plus App.tsx
and Layout.tsx.
Known deferrals not addressed in this commit:
- MobileTabBar does not port MobileBottomNav's "new issue" FAB or
inbox badge count. Spec section 9.1 says 4 destinations only;
requires user decision on whether to restore as a separate mobile
affordance or route through the command palette.
- VoiceMicButton.tsx and useVadRecorder.ts are now dead code after
phase 14's ChatInput migration. Both are in the phase 16 cleanup
plan's deletion list.
- Destination regexes are duplicated between IconRail and
MobileTabBar. Phase 16 DRY target via a shared frame/destinations
module.
- Phase 13 left ui/src/lib/instance-settings.ts whitelisting
/heartbeats and /experimental paths. Redirect routes catch these,
so the whitelist is cosmetic; phase 16 cleanup target.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrites InstanceGeneralSettings.tsx into the consolidated /instance/
settings/general destination per spec §8.1 — a single-column scroll
that stacks eight section cards (Workspace, Local AI, Cloud Providers,
Skills, Routines, Telegram, About, Danger Zone).
App.tsx nested settings sub-routes collapse:
- /instance/settings/heartbeats → redirect to /general
- /instance/settings/experimental → redirect to /general
The /instance/settings/plugins tree is left intact: it is a
plugin-system surface with its own pages, not a settings sub-page.
Deletes InstanceSidebar.tsx (already unmounted by Phase 8),
InstanceSettings.tsx (scheduler heartbeats dashboard, folded out per
spec), and InstanceExperimentalSettings.tsx (experimental toggles are
now part of the Workspace section).
Also fixes ToastInput shape (title/tone instead of message/type) in
CloudProvidersSection and TelegramSection, and drops the unavailable
cronExpression access in RoutinesSection since the RoutineListItem
trigger Pick does not include it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TelegramSection wraps /telegram/token (masked password Input) and
/telegram/status so the bot token can be set or replaced inline.
Values are never logged.
AboutSection renders app name, version, fork lineage, and MIT license
with an outbound link.
DangerZoneSection marks the reset-workspace and delete-all-
conversations actions from spec §8.1 as disabled placeholders — their
server endpoints are not yet wired, and fabricating fake ones would
break the "preserve existing functionality" rule.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
main.tsx adds VoiceProvider and CommandPaletteProvider to the provider
stack, placed above BrowserRouter so both the route tree and the
document-level Cmd+K listener see a stable context.
PersonalAssistant gains a single-shot effect that, on mount, drains
VoiceContext.queue via drainQueue() and feeds each transcript through
the existing handleSend pipeline. This implements spec section 5.5
(voice captured on non-Assistant routes streams through on arrival).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The inline voice button is now a thin consumer of VoiceContext: it
calls startListening({ inline: true, onTranscript }) so the transcript
is inserted into the textarea instead of being queued for the
Assistant inbox. The useVadRecorder-based VoiceMicButton import is
dropped — getUserMedia, MediaRecorder, and the /api/transcribe fetch
all live in VoiceContext now. VoiceWaveform is still rendered while
listening, driven by the shared mediaStream from the provider.
Tests wrap ChatInput in <VoiceProvider silenceErrors> so the
useVoice() hook resolves in jsdom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SkillsSection surfaces installed skill and skill-group counts and
links out to the existing Skill Aggregator (CompanySkills page) at
/<prefix>/skills. The full browse/install/assign UI stays put; the
section is a compact summary + entry point per plan recommendation.
RoutinesSection renders the top five routines as a compact read-only
list (title, cron label, status) and links out to /<prefix>/routines
for the full interactive editor. Single responsibility per page,
matches the plan's recommendation (a) for Routines fold-in.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CommandPalette now consumes CommandPaletteContext (open/setOpen) and
the internal Cmd+K useEffect is gone — the provider owns the global
listener. The search index gains live groups for conversations (via
chatApi.listConversations) and studio workshops (mapped from the
phase 10 WorkshopSlugs), plus stubbed entries for 8 settings section
anchors that will resolve once phase 13 splits InstanceGeneralSettings
into cards. Recipes are stubbed as a disabled placeholder pending
v1.8's recipe API.
useKeyboardShortcuts: adds `onSearch` to the destructure (the phase
6/11 review noted it was referenced at line 25 but never destructured
at lines 12-17) and adds it to the useEffect dependency list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GlobalMicButton drops the state and onClick scaffolding props from
phase 8 and now consumes VoiceContext directly — click calls
toggleListening(). It also reflects hasQueuedVoice via the idle dot
color and an updated aria-label so the assistant-bound queue is
observable from any route.
CmdKButton replaces the synthetic Meta+K keydown shim with a direct
setOpen(true) call on the CommandPaletteContext. The shim comment
block is gone; no more document dispatch side effects on click.
Tests updated to render inside their respective providers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a provider that owns the palette open state and installs a
single document-level Cmd+K / Ctrl+K listener. Replaces the Phase 8
pattern where CmdKButton synthesized KeyboardEvents and CommandPalette
attached its own listener inside a useEffect.
Used by CmdKButton (setOpen(true)), CommandPalette (open/setOpen), and
Layout's onSearch wiring. Tests cover direct toggle, provider-level
keyboard listener (both metaKey and ctrlKey), and the missing-provider
throw path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lifts MediaStream, recording state, transcription buffer, and queue for
non-Assistant captures out of ChatInput's internal VoiceMicButton. The
provider owns the POST /api/transcribe fetch (v1.6 pipeline, unchanged),
exposes idle/listening/speaking state to the top-strip GlobalMicButton,
and queues transcripts captured away from /assistant for PersonalAssistant
to drain on mount.
Per spec sections 4.2 (mic states), 5.5 (voice from non-Assistant modes),
and 10.3 (voice as global affordance). Tests use manual createRoot + act
with a mocked getUserMedia injector to stay deterministic in jsdom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace legacy MobileBottomNav with the new 4-destination MobileTabBar
that mirrors the desktop IconRail (Assistant, Studio, Projects,
Settings). Adds volt active states with a 2px bar above the icon,
safe-area bottom padding, and scroll-hide wiring via the existing
mobileNavVisible handler in Layout.
Adapts Phase 9 HistorySheet and MemorySheet to a full-screen variant
below 768px via useMediaQuery, and makes Phase 11 BuilderTabStrip
horizontally scrollable with scroll-snap and edge fade on mobile.
Light TopStrip polish (tighter padding, truncating breadcrumb)
completes the mobile frame.
PromoteTransition mobile variant is deferred — Phase 12 had not yet
landed when Phase 15 started; the controller can add the mobile
branch when merging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LocalAISection surfaces the Hermes adapter tier, Whisper/Piper voice
availability, and the global voice toggle wired through nexus-settings.
CloudProvidersSection lists Anthropic and OpenAI API key slots; keys
are set via the existing /api-keys/store endpoint and rendered as a
password-type Input so values are masked on display and never logged.
Presence is derived from the workspace secret vault.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 12 — renders the 700ms compress-and-rise transition from spec
§5.6 using CSS keyframes inlined via a scoped <style> tag. Chat ribbon
compresses to 30vh with an inset shadow (0–200ms), brainstormer panel
slides up from the bottom (200–500ms), SOURCE CONVERSATION label fades
in (500–700ms). Honors prefers-reduced-motion. No motion library.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 12 — the form that rises into the bottom 70% during the promote
animation. Collects goal (required), acceptance criteria (one per
line), default gate checklist, and engineer template picker. ESC key
cancels when not mid-create; goal field autofocuses on mount.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 12 — idle/prompting/creating/done/error state machine that drives
the 700ms compress-and-rise animation and calls projectsApi.create with
a brainstormer payload (goal, acceptance criteria, gates, engineer
template). Also exports buildCreatePayload helper for testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First of eight section cards for the consolidated Settings page. Folds
in theme toggle, re-run onboarding trigger, keyboard shortcuts, log
censoring, feedback sharing preference, and the two experimental
toggles (isolated workspaces, dev-server auto-restart) that previously
lived at /instance/settings/experimental.
Preserves all existing form validation, save handlers, and error
toasting from InstanceGeneralSettings and InstanceExperimentalSettings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared section card shell for the Nexus Settings page per spec §8.2:
1px charcoal border, 8px radius, transparent fill, uppercase silver
title with hairline rule. Used by all 8 section cards that replace the
nested Paperclip instance-settings tree.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three coordinated changes after reviewing the wave 2 subagent reports:
1. Restore Presentations as the 9th Studio workshop.
Phase 10's subagent dropped Presentations from the workshop grid
because the spec's eight-workshop list didn't include it. But
.planning/PROJECT.md explicitly lists "Presentations & video
generation via Remotion" as an Active v1.7 requirement and the
existing PresentationPanel.tsx is already a real working Remotion
generator. Dropping it was silent feature regression.
- workshops.ts: add "presentations" slug + Presentation Lucide
icon, placed between social and convert in canonical order
- classifyIntent.ts: add pitch-deck / slide-deck / demo-video /
keynote intent routing (before social so "pitch deck" wins)
- StudioWorkshopDetail.tsx: import PresentationPanel and add a
"presentation" case in WorkshopBody
- workshops.test.ts: expected canonical order updated to 9 slugs
- classifyIntent.test.ts: 4 new parameterized rows for presentations
- WorkshopCard.test.tsx: index 7 is now PRESENTATIONS, 8 is CONVERT
- WorkshopGrid.test.tsx: expected card count 9, canonical order
2. ProjectCard hero-stat derivatives instead of em-dash city.
The shared Project record has none of the fields the spec §7.1 card
depends on: milestoneProgress, nextGate, costBurned, per-project
agent count, phase/milestone array. Wave 2 shipped every card with
"—%" and blank hero numbers — visually underwhelming for a layout
whose whole point is the 72px volt performance stat.
Compute best-effort proxies on the client from data that exists:
- progress: closed_issues / total_issues × 100, from a single
issuesApi.list(companyId) query grouped by projectId
- nextGateName: first pending approval whose payload.projectId
matches, from a single approvalsApi.list(companyId, "pending")
query
- lastActivity: max(project.updatedAt, newest issue.updatedAt in
the project), rendered as "8m ago"-style diff
Each proxy is annotated with // TODO(phase-11.5) for replacement
when real backend aggregates land. phase, costBurnedCents, and
per-project agent count remain hard gaps — rendered as null which
the card surfaces as em-dashes. These three are explicitly queued
for Phase 11.5.
No backend changes; everything derives from existing endpoints.
Two new useQuery calls in Projects.tsx (issues + pending approvals)
both fire per-company, not per-project, so they stay cheap for the
~dozens-of-projects scale the list targets.
3. Spec updated 8 → 9 workshops everywhere it referred to the count.
docs/specs/2026-04-11-nexus-layout-overhaul.md:
- §2 IA table: 8 → 9 workshops
- §6 ASCII header: Eight → Nine
- §6.3 section title: Eight workshops → Nine workshops
- §11 decisions log #16: amendment note explaining the 8→9 bump
- §13 phase 10 description: 8-card → 9-card, with Presentations
explicitly called out
- "Folds into Studio as the 8th workshop" → "Folds into Studio
as a workshop (the legacy /convert route is preserved for
backwards compat)"
Verification: 75/75 studio tests passing; 52/52 projects tests
passing; tsc clean on studio/ + projects/ + Projects.tsx +
ProjectDetail.tsx + StudioWorkshopDetail.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Controller integration pass after the three wave 2 subagents (phases
9, 10, 11) completed their phase implementations. Three changes in
one commit because they're a single coordinated post-dispatch step:
1. App.tsx routing
- Adds 5 new per-project builder tab routes for phase 11:
projects/:projectId/agents
projects/:projectId/gates
projects/:projectId/costs
projects/:projectId/activity
projects/:projectId/org
plus their unprefixed UnprefixedBoardRedirect variants so
direct nav and deep links resolve through the same fallback
chain as /overview and /issues.
- Adds content-studio/:workshopSlug as a sibling route for
phase 10's workshop detail view. Without this, clicking a
workshop card hit the * fallback NotFoundPage because the
existing content-studio route was an exact match and the
ContentStudio-internal pathname workaround couldn't fire.
- Does NOT rename the legacy /convert route. ConvertPage still
renders directly at /convert for backwards compat; Studio's
Convert workshop reuses the ConvertPanel body inside its own
detail shell.
2. IconRail volt-dot indicator
- Imports useCompany from CompanyContext and useGateIndicator
from the new phase 11 hook.
- When selectedCompanyId resolves to a company with at least
one pending approval (displayed as "gates" per phase 11's
display rename), renders a 6px volt dot overlay in the
top-right of the Assistant destination icon and updates
the link's aria-label to "Assistant (pending gates)".
- This is the single global notification surface specified by
spec section 10.4 - no badge counts, no inbox icons, no toasts.
3. IconRail.test.tsx
- Mocks useGateIndicator at module scope so tests don't need
a QueryClientProvider for the rail's useQuery-backed data.
- Replaces the plain function mock with a vi.fn() spy so
per-suite overrides can flip hasPendingGates without dynamic
imports.
- Adds a sibling describe block that verifies the volt dot
renders and the aria-label updates when hasPendingGates is true.
- 7 original tests pass; 2 new tests cover dot-absent and
dot-present cases. 9 tests total.
Verification: 211/211 tests passing across 22 files in the combined
frame + assistant + studio + projects suites; tsc clean on every
wave 1 and wave 2 file plus App.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates the Phase 11 Builder tab strip into ProjectDetail without
disturbing any existing Paperclip sub-route behavior. The approach:
1. When location.pathname resolves to a Builder tab (overview /
issues / agents / gates / costs / activity / org), render the
BuilderTabStrip + the matching new tab component (OverviewTab /
ProjectIssuesList / AgentsTab / etc.) and hide the legacy
PageTabBar + legacy overview/list handlers.
2. When the pathname resolves to a legacy tab (configuration /
budget / workspaces / plugin:*), the existing Tabs/PageTabBar and
dispatch render exactly as before — backwards compat preserved.
This is Option 1 from the Phase 11 plan: union the old and new tab
logic, silent pass-through for configuration/budget, no surface in
the new strip.
OverviewTab currently receives null for every data slice
(progress/milestones/originChat/activity24h) because the shared
Project type + projectsApi don't carry those fields yet; the tab
renders explicit em-dash / "No milestones defined" placeholders.
See the Phase 11 report for the backend gap list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 9's Promote action no longer navigates away from the Assistant
route (spec §5.7 says the user stays in Assistant after promotion), so
useNavigate() and its ugly data-nav placeholder span are dead code.
Drop them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the EntityRow table with the spec §7.1 hero-stat card grid:
full-width cards on <1024px, 2-up on lg+, 16px gap. Each card uses
the new ProjectCard component (72px Inter Black volt hero, status
dot, progress bar, sub-line, footer).
Adds the spec's empty state — full-bleed 96px Inter Black volt
"NO PROJECTS YET" with a forest-green "⊕ START YOUR FIRST PROJECT"
CTA. The regular top-right "⊕ NEW PROJECT" CTA is also forest-green.
Both CTAs wire to the existing openNewProject dialog.
Data gaps: the card passes progress=null, phase=null, nextGateName=
null, costBurnedCents=null because the shared Project type doesn't
carry those fields yet. ProjectCard renders em-dash placeholders
gracefully. Status is mapped from the existing ProjectStatus enum
(paused → waiting, active/running → working, else idle).
Also fixes an unrelated test fixture that used the stale "active"
ProjectStatus literal which tsc rejects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Composes the Phase 9 assistant primitives at /assistant: full-bleed chat
canvas with a 760px centered column, AssistantHomeGreeting for the no-
conversation state, AssistantInputBar + ActionStrip anchored at the
bottom, and HistorySheet / MemorySheet slide-overs. Drops the 160px
inner conversation-list column and the custom MessageBubble, delegating
thread rendering to the shared ChatMessageList and streaming to the
shared useStreamingChat hook. Adds a ?prompt= query-param fallback for
Studio → Assistant hand-offs, preserves the existing assistantHandoff
call as the Promote action, and syncs the selected conversation id with
the shared ChatPanelContext so HistorySheet stays in lockstep.
Also fixes typecheck fallout from the rewrite: switches toast tones to
the documented info|success|warn|error set, narrows the stale-project
filter to use archivedAt (ProjectStatus never had "archived"), and
tightens the MemorySheet test render helper to JSX.Element.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires the 6 Builder tab content components:
• IssuesTab — thin wrapper around IssuesList (which already accepts a
projectId prop, so this is a full wiring).
• GatesTab / AgentsTab / CostsTab / ActivityTab / OrgTab — render a
shared TabPlaceholder marking the Phase 11 data gap. None of the
underlying list components accept a projectId filter today, and the
Phase 11 plan forbids modifying them unilaterally. The placeholders
are explicit (never fabricate per-project data) and carry the
follow-up description the controller will turn into a ticket.
GatesTab is named "Gates" everywhere visible (display-only rename per
spec §7.2.4 / plan §5); approvalsApi / Approval type / /api/approvals
endpoints are all untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the default Builder tab per spec §7.2.1:
• 72px Inter Black volt hero percentage + "N AGENTS ACTIVE" counter
• Milestone checklist card with [✓]/[○]/[ ] bullets and pale yellow
next-gate marker + "← NEXT GATE" label
• Optional origin chat card (hidden when project has no origin)
• 24h activity rollup card (commits, issues closed, gates awaiting,
burned)
All data inputs are typed with nullable fields so callers can pass
null for any missing slice and the tab renders explicit em-dash or
"No milestones defined" placeholders without fabricating values.
The Project type doesn't currently carry milestones, origin chat, or
24h rollups — see Phase 11 report for the backend gap list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Right-side 340px slide-over that lists the facts stored in the existing
assistantMemoryApi and lets the user append new facts or clear memory.
Handles loading, error, and empty states, closes on backdrop click or
ESC, and degrades to a workspace prompt when no company is selected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the 7-tab Builder strip rendered under TopStrip on Project
Detail: OVERVIEW · ISSUES · AGENTS · GATES · COSTS · ACTIVITY · ORG.
Inter 600 14px uppercase, 0.1em tracking, silver default, volt text
+ 2px volt underline on active. ORG is hidden for single-agent
projects (spec §7.2.7).
Also exports resolveBuilderTab() and useActiveBuilderTab() for the
ProjectDetail integration. The link targets for agents/gates/costs/
activity/org route back to ProjectDetail via path matching until the
controller adds the new App.tsx routes post-Wave (see report).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Left-side 320px slide-over butted against the icon rail. Wraps the
existing ChatConversationList so conversation grouping, search, pinning
and create/rename flows are preserved as-is. Closes on backdrop click
or ESC and includes a dismiss button in the header.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-column workshop detail shell — params on the left (holds the
existing generator panel), preview placeholder and action bar
(Save/Export/Send to Assistant) on the right. Maps each workshop slug
to one of the legacy ContentStudio generator panels, with ConvertPanel
folded in for the convert workshop and a placeholder fallback for
unknown slugs. Reads ?prompt= from the URL and surfaces it as a chip.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four-button row beneath the input bar — Promote (volt outline, disabled
when the conversation is not promotable), Attach, Memory, History. Pure
presentational component; the caller owns click handlers and the
promotable predicate. Phase 12 will wire the promote animation itself.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New Projects list card: 72px Inter Black volt hero percentage, pale
yellow/volt/forest status dot, 8px progress bar, sub-line with phase
and next-gate counter, footer with burn + last activity. Graceful
em-dash placeholders when milestone/cost/activity fields are missing
(the shared Project type has no milestoneProgress etc. yet — see
Phase 11 data-gap report).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wraps the existing ChatInput and VoiceWaveform into the spec-canonical
input surface: voice waveform above, hairline divider, and text input
below, inside a rounded 8px near-black shell. Stateless composite —
callers pass micStream/micActive and onSend through to the underlying
primitives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Freeform input rendered at the bottom of the Studio home. Uses the
classifyIntent helper to route to a workshop, or falls through to the
Assistant for unclassified prompts. The parent page wires the two
callbacks to navigate().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Renders a conversational home-state message as an assistant-turn bubble
when no chat is active, summarising active agents, pending gates,
recent completions, and stale projects via markdown bullets. Replaces
the old Dashboard grid for the Assistant landing route. Exposes a pure
`buildGreetingMarkdown` helper driving the unit tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a minimal pending-gate indicator hook for Phase 11. Reads
the existing approvals endpoint and exposes {hasPendingGates, count,
loading, error} for the IconRail Assistant dot overlay. The rename
from "Approvals" to "Gates" is display-only — approvalsApi and the
underlying /api/approvals endpoint are untouched.
Ships the hook + tests only; the IconRail wiring is deferred to the
controller post-Wave 2 (IconRail is a Phase 8 file).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Presentational primitives for the Studio home. WorkshopCard renders a
single workshop with title/subtitle/volt icon and fires onSelect on
click. WorkshopGrid lays them out responsively (1/2/3 columns). No
routing concerns here — the ContentStudio page binds onSelect to
navigate().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aggregates active agents, pending gates, recent completions, and stale
projects from the existing agents, approvals, activity, and projects
APIs into a single shape consumed by AssistantHomeGreeting. The pure
`composeHomeStatus` helper drives the tests; the hook wires it to
react-query and degrades each slice to empty on fetch error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce the single source-of-truth data structure for the 8 Studio
workshops (diagrams, icons, themes, wallpapers, documents, brand-kits,
social, convert) and a pure keyword-based intent classifier used by
StudioPromptBar to route freeform prompts to the right workshop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three cleanups from the Task 6 code-quality review:
1. Delete dead instanceSettingsTarget state and supporting code.
The useState, the readRememberedInstanceSettingsPath helper, the
INSTANCE_SETTINGS_MEMORY_KEY constant, and the localStorage-writing
effect were all residue from the old footer sidebar buttons that
Task 6 deleted. Verified via grep that no other file in ui/src
reads paperclip.lastInstanceSettingsPath.
2. Consolidate two near-identical mobile-nav-visibility effects into
one. The standalone isMobile-reset effect was a strict subset of
the scroll-listener effect's early-return branch.
3. Drop onToggleSidebar / onTogglePanel stub callbacks.
useKeyboardShortcuts declares these as optional and uses optional
chaining internally; the stubs were "type contract satisfaction"
the contract did not actually require.
No behavioral change. 38 frame tests still pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrites Layout.tsx to compose the new Phase 8 frame (IconRail +
TopStrip) and remove the old chrome elements specified as killed in
docs/specs/2026-04-11-nexus-layout-overhaul.md §2:
Removed from chrome:
- 280px collapsible Sidebar / InstanceSidebar
- ChatPanel global slide-in right rail
- PropertiesPanel global slide-in right rail
- BreadcrumbBar (replaced by ModeBreadcrumb inside TopStrip)
- Footer row with Docs link, version tooltip, instance settings button,
chat toggle button, theme toggle button
- Effect that closed PropertiesPanel when chat opened
- Mobile sidebar drawer block
- Mobile sidebar swipe gesture listener
Preserved:
- Company-prefix URL sync and fallback redirect machinery
- First-run onboarding trigger
- WorktreeBanner, DevRestartBanner
- Scroll-based mobile nav visibility tracking
- Body overflow management
- Instance settings path memory
- Dialog overlays (NewIssue, NewProject, NewGoal, NewAgent)
- ToastViewport, CommandPalette
- MobileBottomNav (mobile only; Phase 15 replaces)
Added:
- IconRail mount with derived companyPrefix from matchedCompany or
selectedCompany
- TopStrip mount above the main content area
- hasUnknownCompanyPrefix fallback defaults to /assistant instead of
/dashboard (Dashboard is killed in the new IA)
- useKeyboardShortcuts.onSearch dispatches the same synthetic Meta+K
keydown as the CmdKButton shim
The Sidebar, InstanceSidebar, BreadcrumbBar, ChatPanel, PropertiesPanel,
ThemeContext, and useChatPanel files remain in the repo; Phase 16
deletes dead files after the other Phase 8 tasks are proven stable.
Pages render unchanged in the new frame and will look visually wrong
until Phases 9-13 rebuild their internals. That is the expected
intermediate state per the spec.
Part of Phase 8 of the Nexus layout overhaul (task 6 of 7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
48px sticky top strip per docs/specs/2026-04-11-nexus-layout-overhaul.md
§4.2. Composes ModeBreadcrumb (left) + CmdKButton and GlobalMicButton
(right) inside a <header aria-label="Top bar"> landmark.
Completes the frame component set for Phase 8. The next task (task 6)
rewrites Layout.tsx to mount IconRail + TopStrip as the new global
chrome and delete the old sidebar/ChatPanel/PropertiesPanel/
BreadcrumbBar combination.
Part of Phase 8 of the Nexus layout overhaul (task 5 of 7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Visual-only mic button for the top strip per
docs/specs/2026-04-11-nexus-layout-overhaul.md §4.2. Renders three
specified states (idle / listening / speaking) but Phase 8 only
wires the idle state functionally. Phase 14 will toggle the state
prop from the voice pipeline without changing this component's
signature.
Uses text-primary/bg-primary for volt (already migrated in phases
1-3) and literal #166534 / #a0a0a0 for forest and silver, which
MIGRATION-PLAN.md §3 proposes as new semantic tokens that have not
yet shipped.
Part of Phase 8 of the Nexus layout overhaul (task 4 of 7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Top-strip button that renders the ⌘K glyph and opens the existing
CommandPalette by dispatching a synthetic Meta+K keydown on document,
which CommandPalette.tsx already listens for at its useEffect
(lines 42-51). This is explicitly a Phase 8 shim; Phase 14 of
docs/specs/2026-04-11-nexus-layout-overhaul.md replaces it with a
proper command-palette context when globalizing the palette's
search index.
Part of Phase 8 of the Nexus layout overhaul (task 3 of 7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code-quality review for Task 2 caught a startsWith false-positive:
`/instance/settings-foo` was matching the settings branch of
deriveBreadcrumbSegments and producing nonsense output like
["SETTINGS", "-FOO"]. Tightened the guard to require an exact
match or a literal trailing slash before entering the settings
branch.
Added three test cases to lock it in:
- /instance/settings (exact, no trailing) -> ["SETTINGS"]
- /instance/settings-foo -> ["HOME"]
- /instance/settings-foo/bar -> ["HOME"]
22 tests pass. No behavior change for any previously-tested path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>