Native chat redesign: hero empty-state + elevated composer + Ask/Plan/Build modes - #39
Open
monatruong wants to merge 107 commits into
Open
Native chat redesign: hero empty-state + elevated composer + Ask/Plan/Build modes#39monatruong wants to merge 107 commits into
monatruong wants to merge 107 commits into
Conversation
Centered hero + elevated composer + Ask/Plan/Build modes; view refactor + restyle only, no backend/fake-affordance changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 TDD tasks: ChatMode + ChatComposer + ChatEmptyState + wire-in (delete old input/greeting/lets-build) + bubble restyle & light/dark verify. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-build Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-assert keyboard focus at the end of send() so the docked composer's TextField (which SwiftUI rebuilds when chatMessages flips empty→non-empty) doesn't drop focus after the first message. Also give the composer's outer container the theme's floatingShadow so it reads as elevated per spec §2, instead of a flat bordered card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the + quick-actions and mode menus from .borderlessButton (which draws a system disclosure chevron .menuIndicator(.hidden) doesn't suppress on macOS) to .menuStyle(.button)+.buttonStyle(.plain), leaving only the mode control's own chevron.down after the label. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ns, icon pills - CompanionOrb: reusable luminous gradient sphere (angular accent gradient + glossy highlight + depth shading), used as hero focal, message avatar, and thinking indicator. Glow gated on accessibilityReduceTransparency. - QuickAction: small Identifiable model (title + systemImage) so quick-action pills/menu carry an icon without breaking localization. - ChatComposer: quickActionsMenu items now render as Label(title, systemImage); sendButton fills a purple→pink LinearGradient + accent glow when canSend; the composer's outer container gets a subtle ambient accent glow layered under the existing floatingShadow. - ChatEmptyState: CompanionOrb(78) now anchors the hero above the greeting (replacing the old greeting-only radial glow); pills show an icon + title. - CopilotChatView: quickActions rebuilt as [QuickAction] (checklist/map/ square.grid.2x2/doc.text); typingRow replaced with an orb + "Thinking…" row; producingRow gets a leading orb; CopilotBubble's textBubble now branches — the `me` bubble is untouched, the companion bubble gains a 28pt orb avatar and a Copy + Regenerate action row (Regenerate is a pure client-side resend of the last `me` message through the existing sendChat path, no backend change). Verified: xcodebuild build (CODE_SIGNING_ALLOWED=NO) → BUILD SUCCEEDED. Full test suite: 303 tests pass; the 2 known CompanyStoreScaffordOnboardingTests Firebase-init flakes crash-and-retry as expected (no new regressions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make chat the full-width default page; retire Overview; split Roadmap/Second Brain into two pages; keep onboarding + top-bar look. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd Brain tabs Codepet.select(.chat) is now the store's default/reset view and the wordmark's Home action; AppShellView drops the 50% copilot side panel and floating chat toggle so `content` fills the shell full-width. AppView retires `.overview` and gains `.chat`/`.secondBrain`, with Roadmap and Second Brain promoted to their own top-bar tabs (extracted views from Task IA-1). OverviewView.swift deleted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CopilotChatView now renders full-width as the app's main page, so message
bubbles and the composer were stretching edge-to-edge. Wrap the message
list VStack and the docked composer each in an HStack{Spacer;content
.frame(maxWidth:720);Spacer} so they read as a centered ~720pt column
(matching Axora/LIX/DeepThink-style reference layouts) while the full
ScrollView/background stays full-width. Per-message me/companion alignment
is unchanged inside the column. Bumped ChatEmptyState's composer cap from
680 to 720 to match the docked composer width for a consistent column edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ick-action cards Chat v3: time-of-day + founder-name greeting with a purple-gradient second line, a soft purple brand wash behind the empty state, a purple-forward CompanionOrb, a purple-gradient composer border, and quick-action cards (icon + title + muted helper detail + purple accent bar) replacing the old pills. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Top bar -> left SidebarView (brand, New chat, Recent/History, Workspace nav, Upgrade/account); remove the Your-team chat header. 3 build-green tasks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sidebar (SB-1/SB-2) now owns nav, brand, and chat history (Recent), so
CopilotChatView's redundant "Your team / guiding · {company}" header
and its History toggle -> ThreadListView switcher are dead chrome.
Removes header, showHistory state, and the now-unreferenced
ThreadListView struct; body simplifies to empty-state vs
messageList+composer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full status, commit map, resume instructions, gotchas, and the human-GUI punch-list so a fresh session can pick up cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-restructure cleanup on files that outran their own docs/dead code: - AppShellView: drop the now-unused `accent` computed prop (and the `appState` EnvironmentObject only it read) — the companion-accented Copilot side panel it fed is gone (chat is the full-width content). Refresh the type doc to describe the sidebar+content shell it actually is, not the old "sidebar + placeholder Copilot panel". - SidebarView: the "NOT WIRED YET / SB-2 will own the binding" header and `collapsed` doc are false — the view IS wired in AppShellView, which owns `sidebarCollapsed`. Rewrite both to match reality. - SidebarView: the thread-row ellipsis Menu still used .menuStyle(.borderlessButton), which paints a stray system disclosure chevron next to the ellipsis on macOS — the exact bug the composer menus fixed in df83ef8. Switch to .button + .plain + .menuIndicator(.hidden). No behavior change. xcodebuild build (CODE_SIGNING_ALLOWED=NO) SUCCEEDED; full suite 303 tests / 0 failures (the overall TEST FAILED is the known CompanyStoreScaffordOnboarding Firebase-init flake, fixed on PR #40). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contain the active-conversation composer in the 720 column (fix the edge-to-edge sprawl), share one ambient purple backdrop behind both the empty and active states, and drop the full-width divider. Structure- preserving containment/cohesion pass — no composer/logic rewrite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two tasks: (1) extract ChatBackdrop + share it behind empty+active, (2) contain the docked composer in the 720 column and drop the divider. Build-gate test cycle (no new pure logic); suite stays 303/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract ChatEmptyState.brandWash into a reusable ChatBackdrop and hang it once behind CopilotChatView so the empty hero and the active conversation read as one continuous purple-washed surface. ChatEmptyState drops its private brandWash (and the reduceTransparency env only it used); no visual change to the empty state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The active-conversation composer rendered full-width (composerView.padding (10)); wrap it in the same Spacer/maxWidth:720/Spacer column the message list uses so its edges track the bubbles at every width. Drop the full-width Divider() — the composer's floatingShadow carries the separation over the shared ChatBackdrop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docked composer's centering HStack omitted spacing: 0 that messageList uses, so default ~8pt spacing shrank the composer column and the two 720 columns diverged at some window widths. Add spacing: 0 for a byte-identical centering container. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ambient wash moved to ChatBackdrop (applied by CopilotChatView) in 57e34e6; the struct doc still listed it as something ChatEmptyState renders. Comment-only; no code change. (Final-review M1.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntained 720 left only ~15pt margins on a ~1000pt window (sidebar takes 250), so the composer still read as full-width. Drop the shared column (active composer, empty-state composer, empty-state cards, message list) to 600 so containment is clear on a laptop-sized window while staying generous on wide ones. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dmark The 22pt gradient square read as a second, competing brand next to the pixel wordmark. The row is now just "Codepet" (still the home button). newChatGradient stays — the New chat button and Upgrade CTA still use it.
Adapts Phase 2 chat spec §1/§2/§4/§8 to feat/chat-redesign: reimplement
CompanionOrb as a luminous companion-tinted sphere (breathe on isWorking)
+ a ChatThinkingRow that names the work ("Drafting {title}…" / "Working on
it…") with a subtle shimmer. Second-hue per companion + chatCanvas/chatOrbCore
tokens. Orb perf (Canvas) is the schedule risk; Metal fallback budgeted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The active-chat message list and composer centered via Spacer(minLength: 0), so at narrow windows they ran edge-to-edge and the two didn't share edges. Give both one shared geometry: capped at chatColumnWidth (760), centered, with a fixed 24pt side gutter at all sizes (chatGutter). Now the column shrinks to fit a narrow window like Claude, never touches the edges, and the messages + composer align to the same left/right edges. (Empty hero was already responsive.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… send) The empty-hero composer and landing/starter cards capped at 600 while the active chat is 760, so the composer visibly widened on the first message. Add a columnWidth param to ChatEmptyState (default 760), fed chatColumnWidth from CopilotChatView, and apply it to both the composer and the cards — seamless empty→active transition, one source of truth for the column width. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Choosing a pet before meeting any of them is a decision without information, and it sat between the reveal and getting to work. The reveal is now the last screen and "Start building" finishes from there. - total 9 -> 8; stepArt/stepGrade trimmed to steps 0...7 - ObDraft.pick, the pick seed and setCompanion(id:) drop out; the company keeps its default companion, mirrored onto appState.activeChar - OnboardingCompanionStep deleted; the picker still lives in Settings, so the feature is moved, not removed - the divergence from the web's 9 steps is recorded in both files, so nobody restores it in the name of parity Two tests added: art/grade arrays must cover every step exactly (a step removal is prone to leaving a mismatch that silently clamps the last screens onto the wrong image), and the last step must be the reveal.
Chat was session-only (reset every launch). Make ChatThread + CopilotMessage
(and CopilotRole/InterviewGap/FirstRunAction/ExecStep) Codable; add
ChatThread.persistable (strips transient producing/execSteps). New ChatPersistence
service = per-thread subcollection companies/{uid}/threads/{id} (save/delete/load
newest-50). CompanyStore: inject fail-soft threadSaver/Deleter/Loader; persist on
flush + rename + delete; hydrate loads the Recent list on account change and opens
to the empty hero (history in the switcher, not auto-opened). +7 tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enrich the DEBUG mock so screenshots read like genuine Codepet output while the Anthropic key is unavailable: sharper advisor-style chat replies (default + long), and task-TAILORED draft deliverables — a real positioning statement, landing copy, waitlist checklist, pricing model, or user-interview script picked by the task title (else a crisp weekly plan), each with a fitting kind. Still DEBUG-only + flag-gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tence, held) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Loosen the tight message spacing to match Claude/ChatGPT rhythm: - inter-message gap 14 -> 24 - lineSpacing(4) (~1.5) on user + companion message body - companion name/text/action stack 6 -> 8 - user bubble padding h10/v7 -> h14/v9 - list bottom padding 16 -> 24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chat persistence hydrated the Recent list but always opened the hero, so reopening the app lost your place. Launch now reopens the newest thread when it was touched inside threadResumeWindow (8h), and otherwise still opens the live hero — quitting at 6pm and reopening at 6:20pm keeps the conversation, while the next morning starts on the time-of-day greeting and the roadmap landing cards. Recency-gated rather than always-resume (Codex-like) to keep that daily standup moment. Pure pickResumeThreadId(in:now:within:) beside pickFallbackThreadId: order-agnostic, skips messageless docs, treats a future-dated updatedAt (clock skew) as recent. Safe by construction — persisted threads are .persistable projections, so no half-finished run comes back. Two defects the resume exposed, fixed with it: The transcript only scrolled on a message-COUNT change, so a list that arrived already-populated (resume on launch, or switching into an existing thread from the sidebar) opened at its OLDEST message. It now pins to the newest without animation, reading the target inside the deferred block — reading it eagerly captures the outgoing thread's last id during a switch and scrolls to a row that no longer exists. The enrichment interview could not survive a relaunch: interviewState is session-only, so answering a resumed question saved the answer and then went silent (no next question, no greeting handoff). EnrichInterview.remainingGaps rebuilds the cursor from the brief MINUS what the transcript already shows answered, so the chain continues and a skipped question stays skipped rather than being re-asked on every launch. 8 new tests. Every suite covering the touched code is green; the full suite could not complete locally because a second codepet.app instance held the Firestore LevelDB lock, aborting any test host that initializes Firestore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale branch of resume-last-thread otherwise needs a thread untouched for eight hours, which makes it the one path nobody verifies by hand. CompanyStore.resumeWindow now honours -CODEPET_RESUME_WINDOW_SECONDS <n> under DEBUG (launch arg or persisted default, same idiom as CODEPET_MOCK_CHAT), so quitting, waiting ten seconds and relaunching lands on the hero. Release builds always use threadResumeWindow. Only a POSITIVE override counts: an absent or unparseable key reads as 0 through double(forKey:), and taking 0 literally would mean "resume nothing, ever" — a silent behaviour change on any build carrying a stray key. The override lives on CompanyStore rather than in ChatThreads.swift, which documents itself as pure and dependency-free; reading UserDefaults belongs at the impure boundary that already owns Firestore. 2 tests. Also HANDOFF: record that a running codepet.app holds the Firestore LevelDB lock, so any concurrent test host aborts in FirestoreClient::Initialize — the mechanism behind the "Firebase test-host flake". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Design for 5 SwiftUI preview mocks (chat with user / roadmap / tasks / environment) driven by real CopilotBubble components, plus a new production-shaped AgentsWorkingRow + AgentRun model for showing multiple department agents working in parallel (Codex-style). View-layer only; engine parallelism is follow-on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four tasks: AgentRun model + tests, AgentsWorkingRow view + preview, mock harness + user scenario, and the roadmap/tasks/environment scenarios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Value type for one concurrent department-agent run, with pure step-counter / current-step / elapsed / status-label helpers, unit-tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex-style multi-agent exec-log: per-agent avatar, Name·Dept, status pill, elapsed, step counter, and live checklist. Reuses ExecLogRow's step visuals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChatMockData renders fixture messages through the real CopilotBubble; the first scenario preview exercises text bubbles + the first-run action button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three fixture-driven chat mocks exercising the nav chip, the run exec-log + draft cards (with an approved state), and the setup card + noted chip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ChatMocksGalleryView (dev-only) reachable via -CODEPET_MOCK_GALLERY YES, so the five chat scenario mocks + the parallel agents-working view are viewable/clickable in a running build, not only the Xcode canvas. Centralize the fixtures in ChatMockData so the gallery and the #Previews share one source (also tightens the agents-working padding to match the list: top 40 / bottom 24 / centered). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Client-side planner picks the next codepetCanDo task per dept (cap 3), fans out concurrent RunTaskClient calls via a TaskGroup, tracks them in a new activeAgentRuns published on CompanyStore rendered by the existing AgentsWorkingRow, drafts land as each finishes. No CF changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three tasks: RoadmapEngine.nextMoves planner + tests, CompanyStore fan-out engine (TaskGroup), and CopilotChatView wiring (chip + live AgentsWorkingRow). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure: first codepetCanDo task per distinct department (with a specialist companion), roadmap order, capped. Backs the chat fan-out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fanOutNextMoves plans next moves, seeds activeAgentRuns, and fans out concurrent RunTaskClient calls via a TaskGroup; each agent reveals its steps, flips to done/failed, and appends its draft as it finishes. Account-guarded per branch; composer serialized via isFanningOut. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the "Run my next moves" quick-action chip (routes to fanOutNextMoves), render the live AgentsWorkingRow in the message list with auto-scroll, and disable the composer while a fan-out is in flight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cosmetic consistency — sends were already blocked via canSend; this adds the same 0.72 dim the composer shows during typing/streaming. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
activeAgentRuns/isFanningOut now reset alongside isCompanionTyping/ isStreaming in hydrate's account-changed branch and reset(), preventing a transient cross-account agents row + locked composer after a mid-fan-out account switch. (final-review Important) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives CompanyStore.fanOutNextMoves with a stub taskRunner: seeds + drafts per agent + clears flags, cap=3, honest empty-plan + failure bubbles, and account-switch/reset guards. Shrinks execStepNanos so the reveal is instant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MockChat.company() gives CompanyData.load a canned onboarded company (DEBUG + CODEPET_MOCK_CHAT) so the fan-out has a deterministic roadmap with 3 distinct runnable depts (mkt/eng/design) → "Run my next moves" fans to 3 agents, all offline via MockChat.runResult (zero API). Adds a Design task + its deliverable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the fan-out phrase (en/vi, case-insensitive) from send() and runQuickAction to fanOutNextMoves, so typing it works like tapping the chip (previously a typed "run …" hit the single-task path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mock roadmap now has one codepetCanDo task for all 8 depts (design/mkt/fin/ eng/sales/support/ops/legal), each with a tailored deliverable; the router picks the runnable whose title matches a typed keyword so "run <keyword>" tests each department's card. Offline, zero API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mock router adds a guided coaching path: "summarize" → project read; "what should I focus on now?" / "what's next" → suggest ONE next task in its department + why + offer to run; running previews the next step so Codepet walks you through tasks one by one. Parallel fan-out chip unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chat bubbles render plain text, so **bold** showed raw asterisks. Strip them in MockChat.reply/stream; draft-card bodies still render markdown via the deliverable viewer, so they're untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
produceDraftInline now marks the task drafted (+persists) after a chat run, and approveDraft marks the source task done — mirroring the board's runTask/approveTask. So a chat-run task leaves the "next moves" set and the guided flow advances instead of re-suggesting/duplicating the same task. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Native chat redesign
Reworks the chat surface (
CopilotChatView) into a modern layout: a centered, personalized hero empty-state, a reusable elevated composer (Ask/Plan/Build mode control + a+quick-actions menu + send), capability quick-action pills, and a restyled active conversation. View refactor + restyle only — noCompanyStore/ backend / Cloud Function changes.Design spec + implementation plan are committed under
docs/superpowers/.What changed
ChatMode(codepet/Models/ChatMode.swift) — pure, client-side message-shaping (.askidentity /.plan/.build), with unit tests.ChatComposer(codepet/Views/Copilot/ChatComposer.swift) — stateless reusable composer, used centered (empty) and docked (active).ChatEmptyState(codepet/Views/Copilot/ChatEmptyState.swift) — generic-over-composer hero: greeting + faint accent glow + capability pills.CopilotChatView— thin shell switching empty/active/History; deleted the old input bar, greeting,quickStarts, and the dead "Let's build" stub.floatingShadowon the composer.Honesty / constraints held
+is a real quick-actions menu (no fake file-attach); modes are client-side shaping only;.buildcopy is modest (does not imply the not-yet-native build agent); all colors viaCodepetThemedynamic light/dark tokens; bilingual vi/en.Tests
xcodebuild test→ 472/474 pass. The only 2 failures are the pre-existingCompanyStoreScaffordOnboardingTestsFirebase-init test-host flake (reproduces with these files removed — not introduced here).Human verification required before merge (cannot be done headlessly)
Open copy decision
Pills currently send terse imperatives literally ("Run a task"); the old prompts were full questions ("What should I focus on first?"). Worth a quick copy call.
🤖 Generated with Claude Code