cbe9e5b294
* chore(desktop): literal comments in the guide script and runbooks Comment-only change to onboarding-script.ts and setup-profile.ts. The module headers now state the purpose and the constraints that shaped each file. The notes beside the runbook strings keep one fact per sentence, or are deleted when the string beside them says the same thing. The runbook text, the persona, the option pills and the SOUL text are unchanged. Both versions transpile to identical output with comments removed. * chore(desktop): literal comments in the guide chat cards and stores Comment-only change to the guided chat's cards, directive dispatcher, option catalog, assembly module and chip. Metaphor and personification are replaced by the name of the atom, effect or CSS property they stood for. Comments that restate the code are deleted. Two stale facts are corrected in place: the mini layout trees point at app/contrib/layout-presets.ts, and the skip button sets the onboarding phase to skipped rather than done. One comment line in cards/frame.tsx from bb/connector-ui-e2e-v2 loses a metaphor and an em dash; its fact is unchanged. * chore(desktop): literal comments in the handoff and first build Comment-only change to the handoff wiring, the kickoff, the receipt store, the first-build check-ins, the handoff tour, the connector rows and the machine profile store. Every kept comment names the caller, the constraint or the defect it prevents. The claim that the tour never throws is removed: the function can reject and its caller does not catch. Five comment blocks in connector-tool.tsx written on bb/connector-ui-e2e-v2 lose personification, dramatic capitals and em dashes. Every fact in them stays, and no block moves. * chore(desktop): literal comments in the intro reveal Comment-only change to the intro reveal's clock, timeline, cube renderer, sound, scenes, store and README. Animation comments now name the actual ramp, easing or offset with its number. Four comments that contradicted the code are corrected: the first texture slot opens at 3700 ms, the tear settles from 1 to 0 over 460 ms, the typing weight delays the character it sits on, and INTRO_EXIT_MS is wall time in index.tsx but score time in the overlay. * chore(desktop): literal comments in the Electron onboarding windows Comment-only change to the window growth geometry and the two onboarding windows. The 768 px floor keeps its one fact: the floor uses Math.ceil where the deltas round, because rounding 906.24 DIP down leaves the media query false. The comment that placed the CSS-pixel to DIP conversion at getBounds now points at growWindowBounds, where it happens. * chore(gateway): literal docstrings in the onboarding RPCs and the tour tool Docstring and comment-only change. The module summaries state what each module does and where authorization comes from, without contrast pairs. The tool descriptions the model reads are unchanged. Two words in the tour tool's module docstring lose personification; the rest of that docstring is as it was. ast.dump of both versions, with docstrings stripped, is identical for all three files. * chore(desktop): literal punctuation in the relaunch and film-end notes Comment-only change to four lines that bb/connector-ui-e2e-v2 added to the boot gate, the gate store and the intro gate. Each em dash becomes a colon, a full stop or a pair of parentheses; one emphasis capital is lowercased. The facts in the notes are unchanged.
198 lines
7.7 KiB
TypeScript
198 lines
7.7 KiB
TypeScript
/**
|
||
* The three setup cards: accent, connectors, and layout. The accent and layout picks apply as soon as they are
|
||
* clicked; the connector picks are only recorded. The option lists come from onboarding-chat/options.tsx, so the cards
|
||
* and the previews stay in agreement without the model listing the options.
|
||
*/
|
||
|
||
import { useStore } from '@nanostores/react'
|
||
import { useMemo, useState } from 'react'
|
||
|
||
import { useSessionView } from '@/app/chat/session-view'
|
||
import { $chatLayoutPicked, assembleChatOnboarding } from '@/components/onboarding-chat/assembly'
|
||
import { CardFrame, type CardProps, useCardCommit } from '@/components/onboarding-chat/cards/frame'
|
||
import { Chip } from '@/components/onboarding-chat/chip'
|
||
import {
|
||
accentsFor,
|
||
AccentSwatch,
|
||
LayoutPreviewCard,
|
||
LAYOUTS,
|
||
NOUS_ACCENT,
|
||
orderConnectorPicks
|
||
} from '@/components/onboarding-chat/options'
|
||
import type { LayoutNode } from '@/components/pane-shell/tree/model'
|
||
import { ConnectorLogo } from '@/components/ui/connector-logo'
|
||
import { SearchField } from '@/components/ui/search-field'
|
||
import { registry } from '@/contrib/registry'
|
||
import { connectorTitle } from '@/lib/connector-tools'
|
||
import { useConnectorCatalog } from '@/store/connector-catalog'
|
||
import { $onboardingAnswers, setOnboardingAnswers } from '@/store/onboarding-answers'
|
||
import { useTheme } from '@/themes'
|
||
import { setAccentOverride } from '@/themes/accent-override'
|
||
|
||
export function ConnectorsCard({ locked }: CardProps) {
|
||
const view = useSessionView()
|
||
const storedId = useStore(view.$storedId)
|
||
const runtimeId = useStore(view.$runtimeId)
|
||
const answers = useStore($onboardingAnswers)
|
||
const { commit, done } = useCardCommit('connectors')
|
||
const catalog = useConnectorCatalog(storedId, runtimeId)
|
||
const [query, setQuery] = useState('')
|
||
|
||
// Only what the gateway carries. A pick is a slug the build chat can hand
|
||
// straight to manage_connections; a name the gateway does not carry would be
|
||
// a pick the build chat cannot honour.
|
||
const rows = useMemo(() => (catalog.status === 'ready' ? orderConnectorPicks(catalog.rows) : []), [catalog])
|
||
const shown = rows.filter(row => connectorTitle(row.connector).toLowerCase().includes(query.toLowerCase()))
|
||
const picked = rows.filter(row => answers.connectors.includes(row.connector))
|
||
|
||
const toggle = (id: string) =>
|
||
setOnboardingAnswers({
|
||
connectors: answers.connectors.includes(id)
|
||
? answers.connectors.filter(item => item !== id)
|
||
: [...answers.connectors, id]
|
||
})
|
||
|
||
// Nothing to pick from: the toolset is off or the gateway is unreachable.
|
||
// The step still has to end, so the card offers Skip.
|
||
if (catalog.status === 'unavailable' || (catalog.status === 'ready' && rows.length === 0)) {
|
||
return (
|
||
<CardFrame continueLabel="Skip this" done={done} locked={locked} onContinue={() => commit('apps I use: none for now')}>
|
||
<p className="text-sm text-muted-foreground">Connections aren’t available right now — this can be set up later.</p>
|
||
</CardFrame>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<CardFrame
|
||
continueLabel={picked.length > 0 ? `Continue with ${picked.length}` : 'None of these'}
|
||
disabled={catalog.status === 'loading'}
|
||
done={done}
|
||
locked={locked}
|
||
onContinue={() => {
|
||
commit(
|
||
`apps I use, not connected yet: ${picked.length > 0 ? picked.map(row => row.connector).join(', ') : 'none for now'}`
|
||
)
|
||
}}
|
||
>
|
||
{catalog.status === 'loading' ? (
|
||
<div className="grid grid-cols-3 gap-2">
|
||
{Array.from({ length: 9 }, (_, index) => (
|
||
<div className="h-10 animate-pulse rounded-lg bg-muted/40" key={index} />
|
||
))}
|
||
</div>
|
||
) : (
|
||
<>
|
||
{rows.length > 12 ? <SearchField onChange={setQuery} placeholder="Find an app" value={query} /> : null}
|
||
<div className="grid max-h-72 grid-cols-3 gap-2 overflow-y-auto">
|
||
{shown.map(row => (
|
||
<Chip
|
||
icon={
|
||
<ConnectorLogo
|
||
className="size-7 rounded-full text-sm"
|
||
connector={{ name: row.connector, title: row.name || connectorTitle(row.connector) }}
|
||
/>
|
||
}
|
||
key={row.connector}
|
||
label={row.name || connectorTitle(row.connector)}
|
||
on={answers.connectors.includes(row.connector)}
|
||
onToggle={() => toggle(row.connector)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
{/* Picking is a preference, not an authorization: nothing is signed into
|
||
here. Saying so is what keeps the Connect cards later from reading as
|
||
a second ask for the same thing. */}
|
||
<p className="text-xs text-muted-foreground">
|
||
<strong className="font-medium text-foreground">Nothing connects yet.</strong> Hermes will offer to link these
|
||
when a task needs them, and asks before reading anything.
|
||
</p>
|
||
</CardFrame>
|
||
)
|
||
}
|
||
|
||
export function LookCard({ locked }: CardProps) {
|
||
const answers = useStore($onboardingAnswers)
|
||
const { renderedMode } = useTheme()
|
||
const { commit, done } = useCardCommit('look')
|
||
const accents = accentsFor(renderedMode === 'dark')
|
||
const accent = answers.accent ?? NOUS_ACCENT
|
||
const picked = accents.find(swatch => swatch.hex === accent.toLowerCase())
|
||
|
||
const pickAccent = (hex: string) => {
|
||
const seed = hex === NOUS_ACCENT ? null : hex
|
||
|
||
setOnboardingAnswers({ accent: seed })
|
||
setAccentOverride(seed)
|
||
}
|
||
|
||
return (
|
||
<CardFrame done={done} locked={locked} onContinue={() => commit(`accent color: ${picked?.name ?? accent}`)}>
|
||
<div className="flex flex-wrap gap-2.5">
|
||
{accents.map(swatch => (
|
||
<AccentSwatch
|
||
active={accent.toLowerCase() === swatch.hex}
|
||
hex={swatch.hex}
|
||
key={swatch.name}
|
||
name={swatch.name}
|
||
onPick={() => pickAccent(swatch.hex)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</CardFrame>
|
||
)
|
||
}
|
||
|
||
export function LayoutCard({ locked }: CardProps) {
|
||
const answers = useStore($onboardingAnswers)
|
||
const { commit, done } = useCardCommit('layout')
|
||
// The stored answer defaults to 'basic', so nothing renders selected and Continue stays disabled until the user
|
||
// clicks. The flag lives in a store because applying the picked layout replaces the pane tree and remounts this
|
||
// card, which would clear local state.
|
||
const picked = useStore($chatLayoutPicked)
|
||
|
||
const pickLayout = (id: string) => {
|
||
$chatLayoutPicked.set(true)
|
||
setOnboardingAnswers({ layout: id })
|
||
|
||
const preset = registry.getArea('layouts').find(contribution => contribution.id === id)
|
||
|
||
if (!preset?.data) {
|
||
return
|
||
}
|
||
|
||
// Every pick goes through assembly, including re-picks. The first pick grows the window and places the panes,
|
||
// holding the chat and the cursor over this card at the same screen position; later picks rearrange in place.
|
||
// Swapping only the preset tree on a re-pick kept the previous layout's dismissals and dock records, and the two
|
||
// layouts came up mixed together.
|
||
// SAFETY: Layout presets declare data: LayoutNode (pane-shell/tree/presets.ts).
|
||
assembleChatOnboarding(preset.id, preset.data as LayoutNode)
|
||
}
|
||
|
||
return (
|
||
<CardFrame
|
||
disabled={!picked}
|
||
done={done}
|
||
locked={locked}
|
||
onContinue={() => {
|
||
const choice = LAYOUTS.find(layout => layout.id === answers.layout)
|
||
|
||
commit(`layout: ${choice?.name ?? answers.layout}`)
|
||
}}
|
||
>
|
||
<div className="grid grid-cols-2 gap-3">
|
||
{LAYOUTS.map(layout => (
|
||
<LayoutPreviewCard
|
||
active={picked && answers.layout === layout.id}
|
||
key={layout.id}
|
||
name={layout.name}
|
||
onSelect={() => pickLayout(layout.id)}
|
||
tree={layout.tree}
|
||
/>
|
||
))}
|
||
</div>
|
||
</CardFrame>
|
||
)
|
||
}
|