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>
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>
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>
Uppercase slash-separated breadcrumb that derives from the current
pathname per docs/specs/2026-04-11-nexus-layout-overhaul.md §4.2.
Leaf segment in text-primary (volt), non-leaf segments in
text-muted-foreground (silver). Pure function deriveBreadcrumbSegments
is exported for unit testing and covers 16 route patterns plus the
catch-all HOME fallback.
The derivation intentionally collapses Phase 11's soon-to-be-demoted
routes (issues/agents/routines/goals/approvals/costs/activity/inbox/
execution-workspaces) under the PROJECTS umbrella segment. When
Phase 11 lands and those routes become /projects/:slug/<tab>, the
derivation will naturally produce PROJECTS / PROJECT-SLUG without
code changes.
Part of Phase 8 of the Nexus layout overhaul (task 2 of 7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses two Important findings from the Task 1 code-quality review:
1. Swap literal #faff69 hex for the text-primary / bg-primary token
(4 sites). The --primary CSS variable is already wired to volt via
MIGRATION-PLAN.md phases 1-3, and 50+ existing files use the token.
The original Task 1 commit landed on literal hex as a self-
documenting placeholder; moving to the token aligns with the rest
of the codebase and makes theme swaps a single-file change.
2. Add focus-visible styles to the Nexus mark link and each
DestinationLink. Previously the component relied on browser-default
outline, which on pure-black canvas + volt icons is both off-palette
and visually weak. Now uses a volt ring with background offset for
clear keyboard focus indication.
No behavior change. All 7 IconRail tests still pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the 56px left icon rail specified in
docs/specs/2026-04-11-nexus-layout-overhaul.md §4.1. Four primary
destinations (Assistant, Studio, Projects, Settings) rendered as Lucide
icons with silver default + volt active state and a 2px volt bar on the
right edge of the active item. Destinations are company-prefixed except
Settings, which points at the global /instance/settings/general route.
The Studio icon also highlights on /convert because Phase 10 folds
ConvertPage into Studio as a workshop. The Projects icon is the umbrella
for all Phase 11 per-project-tab routes (issues, agents, routines,
goals, approvals, costs, activity, inbox, execution-workspaces).
The rail is not yet mounted in Layout.tsx — that happens in task 6.
Part of the Nexus v1.7 structural overhaul (Phase 8 of MIGRATION-PLAN.md
§8b). Companion tests cover all 4 destinations, active-state derivation,
and aria-current semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stop showing Paperclip's board UI by default. First-time users now
land on Personal Assistant (v1.5), see a Nexus-first sidebar, and the
NexusOnboardingWizard (built in v1.5) actually fires on first run
instead of sitting behind a dead "Start Onboarding" button click.
App.tsx
- CompanyRootRedirect now reads useNexusMode() and lands the user
at /${prefix}/assistant by default. Only project_builder mode
lands at /${prefix}/dashboard. "personal_ai" and "both" (the
default) both go to the Assistant.
- NoCompaniesStartPage gutted: the old "Create your first company"
button is gone. Single-workspace mode doesn't ask users to name
workspaces; the onboarding wizard handles it. Replaced with a
minimal "Setting up your workspace..." loading shim.
- OnboardingRoutePage now auto-opens the wizard on mount when no
companies exist. Closes the dead-button gap: previously the user
had to click "Start Onboarding" to actually get the wizard; now
the wizard opens itself as soon as they land.
Sidebar.tsx
- Restructured around two mode-gated sections:
* Always visible (Nexus essentials): Assistant, Content Studio,
Convert, Inbox, Skills, Settings. Plus the New Issue button and
plugin sidebar items.
* project_builder-only: Work (Issues, Routines, Goals), Projects,
Agents, and the remaining Workspace items (Org, Costs, Activity).
- Top bar no longer renders a company switcher dropdown — single-
workspace mode shows the workspace name as a static label with
the search button beside it.
- Dashboard link removed from the always-visible section. The
default landing is /assistant; users who explicitly want the
Paperclip dashboard can type the URL or switch to project_builder
mode.
Layout.tsx
- Removed both <CompanyRail /> renderings (mobile and desktop
branches). Single-workspace mode doesn't need a multi-company
icon rail. Import preserved with a [nexus] comment for upstream
rebase compat.
- Onboarding useEffect's authenticated-mode gate removed (root
cause of the v1.5 wizard-not-firing bug on fresh DB). This
effect is now a belt-and-suspenders fallback; the real auto-
trigger lives in OnboardingRoutePage because Layout isn't
actually mounted during the zero-company first-run state
(CompanyRootRedirect navigates to /onboarding before Layout
ever renders).
NexusOnboardingWizard.tsx
- handleSubmit and handleStartChat both used to hardcode the post-
creation navigation to /${prefix}/dashboard. Now mode-aware:
project_builder lands at /dashboard, everything else lands at
/assistant. Matches the Sidebar and CompanyRootRedirect logic —
a fresh user never touches the Paperclip dashboard unless they
explicitly chose project_builder during the wizard.
Not changed:
- The Paperclip pages themselves (Dashboard, Issues, Projects,
Agents, Org, etc.) — still present, still accessible by URL,
still upstream-mergeable. Just hidden from the default nav.
- CompanyRail.tsx, CompanySwitcher.tsx, NewCompanyDialog — files
preserved for upstream rebase diff minimization. No call sites
remain.
- /NEX/companies route still registered in boardRoutes(), just
unlinked from the default UI.
TypeScript: zero new errors (pre-existing errors in AgentConfigForm,
command.tsx, useKeyboardShortcuts, usePiperTts, useVadRecorder,
OnboardingSummaryStep.test, PersonalAssistant unchanged).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First phase of the DESIGN.md (ClickHouse-inspired) migration. Rewrites
the foundation CSS variables and theme machinery; downstream phases
(status/role dictionaries, raw utility sweep) still pending.
index.css
- Full rewrite of @theme inline block. Dark (.dark) and light (:root)
token sets per MIGRATION-PLAN sections 3 and 5:
* Dark: pure black canvas (#000000), Neon Volt primary (#faff69),
Forest Green secondary (#166534), charcoal border (rgba(65,65,65,
0.8)), near-black cards (#141414), silver muted (#a0a0a0).
* Light: near-white canvas (#fafafa), Forest primary, Volt
downgraded to dark olive (#4f5100) for border/active use only,
silver inverted to #6b6b6b. Accessibility fallback, not brand.
- Added --warning (#f59e0b / #b45309), --success, and direct brand
token refs (--volt, --volt-pale, --volt-border, --forest, --near-
black, --hover-gray, --silver, --charcoal-border) exposed as
Tailwind utilities via --color-* mirrors.
- Added --destructive: #ef4444 (#dc2626 in light).
- Radius scale collapsed to 4px sharp / 8px comfortable / 9999px pill.
- Deleted .theme-tokyo-night.dark block entirely (was dead code —
ThemeContext never applied the class).
- Rewrote hljs syntax highlighting: one dark block under .dark .hljs
using volt for keywords, pale volt for strings, silver for
comments; one light block under .hljs using forest/dark-olive/
silver. Replaced all three Catppuccin + Tokyo Night hljs rule sets.
- Rewrote scrollbar rules to use var(--muted) / var(--charcoal-
border) / var(--hover-gray) instead of hardcoded oklch values.
- Added @font-face declarations for Inter (normal + italic) from the
self-hosted woff2 files at /fonts/InterVariable*.woff2. font-weight
100-900 range unlocks weight 900 for DESIGN.md hero moments from
a single variable font.
- Set --font-sans to Inter-first stack; body rule pulls the token.
ThemeContext.tsx
- Simplified to binary Theme = "light" | "dark". Dropped "custom"
theme type, PaletteRole interface, ROLE_TO_TOKEN map, and the
/api/nexus/settings custom-theme hydration effect.
- applyTheme() now just toggles .dark on <html> and sets
colorScheme. applyCustomTheme() left as a deprecated no-op (no
external callers but keeping the export avoids churn).
- Legacy localStorage values (catppuccin-mocha, tokyo-night, custom,
catppuccin-latte) coerced to "dark" on read so existing users
don't see a crash after the migration.
- Default theme: "dark".
Layout.tsx
- Dropped THEME_META import and the THEME_CYCLE map. Theme toggle
is now a binary sun/moon flip via toggleTheme().
index.html
- Added <link rel="preload" href="/fonts/InterVariable.woff2"
as="font" type="font/woff2" crossorigin>.
- Set inline style="background:#000000; color-scheme:dark;" on
<html> so the pre-React paint is already dark — no white flash.
- Boot script coerces legacy localStorage theme values and persists
"light" or "dark" only.
ui/public/fonts/
- Added InterVariable.woff2 (344 KB) and InterVariable-Italic.woff2
(379 KB), both Inter v4.x from rsms.me/inter (the canonical
upstream). Self-hosted for LAN/offline reliability.
Not changed:
- lib/status-colors.ts, lib/agent-role-colors.ts — next phase
- Any component files — phase 3
- MIGRATION-PLAN.md — will be updated with resolved decisions later
Expected visual state: pages using theme tokens (bg-background,
text-muted-foreground, border-border, ~1,250 instances) immediately
render with the new palette. Pages using raw Tailwind utilities
(bg-red-500, text-amber-600, ~274 instances) still show old colors
until phase 3 sweep.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the URL contains a :companyPrefix segment that doesn't match any
fetched company, Layout previously rendered a dead-end NotFoundPage
with no recovery link. The sidebar's company switcher depends on
selectedCompany being non-null, so users landing on a bogus prefix
had no way back without hand-typing a new URL.
Replace the NotFoundPage branch with a redirect to the same path under
the first available company (selectedCompany ?? companies[0]). If the
rest of the path is empty, fall back to /dashboard so the target is
guaranteed to exist. The hasUnknownCompanyPrefix condition is already
gated on companies.length > 0, so the fallback is reachable; the old
NotFoundPage remains as a theoretical safety net if somehow both
selectedCompany and companies[0] are null.
Triggered by a user session after the embedded-postgres wipe: the
browser had a stale localStorage selectedCompanyId and the user
hand-typed URLs with guessed prefixes like /ASSISTANT/company/settings.
Hitting any invalid prefix stranded them on the 404 with no UI to
pick a different company.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add streaming prop (default true) to ChatVoicePlayerProps
- Connect to POST /api/synthesize/stream via fetch + ReadableStream
- Parse SSE lines manually from response body stream
- First sentence audio begins playing as soon as first chunk arrives
- Subsequent sentences auto-play in sequence from audioQueue
- Show 'Sentence N of M' progress indicator during streaming playback
- Dot progress bar shows completed vs pending sentences
- Falls back to full-fetch mode on stream error or streaming=false
- Clean up all object URLs on unmount or new text
- BotFather numbered instructions (4-step setup guide)
- Token input with live validation via POST /api/telegram/token
- Success state showing connected bot username
- Error state with descriptive message
- Skip/Back/Next navigation; Next enabled only after validation
- VoiceModeToggle: Text / Voice In / Full Voice pills with active/inactive styling
- Auto-play checkbox in full_voice mode, persists to nexus:voice:autoplay in localStorage
- useVoiceMode: reads/writes voiceMode via PATCH /api/nexus/settings with loading state
(deviation Rule 3: created missing blocking dependency for VoiceModeToggle)
- VoiceWaveform: 80x32 canvas with Web Audio AnalyserNode (fftSize=64), 20 animated bars drawn from frequency data using --primary color
- VoiceMicButton: three visual states — idle (Mic icon), recording (VoiceWaveform + ring-2 ring-primary), processing (Loader2 animate-spin)
- All three states have correct aria-labels per UI spec copywriting contract
- Add OnboardingSummaryStep as step 5 of the wizard
- Add Skip buttons on step 1 (hardware) and step 2 (mode)
- Replace step 4 form submit with Review & finish -> step 5 flow
- Add Skip to summary on step 4
- Step indicator shows 'Summary' on step 5 instead of 'Step 5 of 4'
- Add deriveProviderLabel helper for provider display text
- Add handleStartChat that creates workspace then calls setChatOpen(true)
- Refactor shared workspace creation into createWorkspace() helper
- ProviderSelectionStep: three provider cards (Puter/Google/API key) with adapter badges
- Cards use border-primary bg-primary/5 when selected (matches ModeSelector pattern)
- PuterAuthButton/GoogleOAuthButton/ApiKeyEntryForm wired via callbacks
- NexusOnboardingWizard: step count 3→4, provider selection at step 3
- Parallel probe for hermes_local/claude_local/openclaw_gateway on wizard open
- Credentials stored after company creation (puterToken, googleOAuthStateId, apiKeyData)
- Skip always advances to step 4; Back from step 4 goes to step 3
- Refactor to 3-step flow: hardware detection, mode selection, root directory
- Add step indicator 'Step N of 3'
- Add HardwareSummaryStep on step 1 with dynamic heading
- Add ModeSelector on step 2 with 'both' pre-selected
- Add Back buttons on steps 2 and 3
- Persist selected mode via updateNexusSettings on wizard completion
- Reset step and mode on wizard close
1. Push notifications: call sendPushToAll after streaming completes
2. Mobile offline: add useOfflineQueue + banners to MobileChatView
3. New conversation streaming: call startStream in Path 1 handleSend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MobileChatView: full-screen mobile chat using 100dvh, back button, safe-area input
- ChatPanel: conditionally renders MobileChatView on mobile via useMediaQuery
- ChatConversationList: wraps ScrollArea in PullToRefresh for mobile
- ChatInput: pb-[env(safe-area-inset-bottom)] padding + 44px Send button touch target
- ChatConversationItem: min-h-[48px] touch target per UI-SPEC
- useMediaQuery: SSR-safe hook with addEventListener for live breakpoint updates
- usePullToRefresh: touch gesture hook with 64px threshold, haptic feedback via navigator.vibrate
- PullToRefresh: visual wrapper with Loader2 spinner, pull/release text indicators
- Add code category branch in ChatFilePreview routing to ChatCodeFilePreview
- Mark FILE-07 (one-click download) as Complete in REQUIREMENTS.md
- Mark FILE-13 (cross-device access) as Complete in REQUIREMENTS.md
- Update Traceability table for FILE-07 and FILE-13
- Add ChatCodeFilePreview component with hljs syntax highlighting
- Fetch file content from contentPath with credentials
- Use DOMParser-based safe rendering (no dangerouslySetInnerHTML)
- Include copy button, language label, and ChatFileCard download below
- Add extToLang extension-to-language mapping
- Register 14 common languages with hljs
- Add highlight.js as direct dependency in ui/package.json
- Add enableVoiceInput prop to ChatInput props interface
- Add handleTranscription callback that appends transcription text to textarea state
- Render VoiceRecordButton conditionally when enableVoiceInput is true
- Pass enableVoiceInput={true} from ChatPanel to ChatInput
- Mark INPUT-02, INPUT-03, INPUT-04 as Complete in REQUIREMENTS.md traceability table