Both servers expose OpenAI-compatible /v1/chat/completions and /v1/models,
so we route them through pi-ai's `openai-completions` API the same way
Fireworks does — but with a per-installation base URL the user enters
during onboarding instead of a hardcoded one. The shared schemas grow a
`local` credential payload carrying just the baseUrl; the credential
resolver surfaces it on `ResolvedCredential.baseUrl`, the runtime bridge
forwards it through `prepareRun`, and the worker plumbs it into
`resolveModel` so the streamed Model<openai-completions> targets the
user's server. The local-cost path short-circuits to zero in cost-writer
since users bill their own hardware, and the live model list is fetched
from the saved baseUrl by `fetchLocalConnectorModels` (Ollama and LM
Studio both speak the standard OpenAI catalog shape). Onboarding ships
a new LocalProviderCard and the Settings panel a LocalForm; both default
to the catalog's per-connector localhost URL and accept any custom URL.
Both the prose-documents and work-products sections rendered as
`{x.length > 0 ? ... : null}` with no else branch, and the whole
DocumentCenter was only mounted when a company was selected. The
combined result: a company with no files gave the operator a page
containing only a title and a "New" button, and an operator who had
not yet picked a company saw nothing at all — the exact "page is blank
even though docs were generated" complaint.
Add an explicit empty state inside DocumentCenter (surfaced only when
both collections are empty) and a separate "select a company" empty
state at the section router level. Strings live in the i18n bundle so
the Chinese locale is not left behind.
The Document Center only read the `documents` table, which is prose
content POSTed via /api/documents. Everything the agents wrote via
`bash` / `write` / `edit` — the bulk of their output on a real
project — landed in `work_products` and never appeared on the page.
Operators saw "0 items" after a full night's run.
Lift work_products into the same UI:
- `listWorkProductsForCompany` returns a newest-first slice with a
500-row cap so the snapshot stays bounded.
- `ProfileSnapshot.workProducts` flows through `useCompanyData` as
`companyWorkProducts`, filtered to the current company.
- `DocumentCenter` renders two labelled sections in order — prose
documents (editable) on top, files & deliverables below. Each
work-product row opens the underlying URL or filesystem path
when clickable, so the agent's bash-generated markdown, PRs,
and artifacts are one click away.
`WorkProductRecord` lifts into `@shared/types/records-extended` so
the renderer, snapshot, and database shells all reference the same
shape. The store-test fixture learns about the new field.
One flat list of `status = in_review` tasks couldn't tell the
operator whether the next decision belonged to the board (approval
pending), to the human user (top-level assignee or explicit
`requires_user_review`), or to a middle manager walking up the
`reports_to` chain. Three very different escalation paths rendered
identically, so "reviewed by X" was the only signal and operators
missed the ones actually waiting on them.
`PendingReviewList` now classifies each in-review task into one of
three kinds (`board` / `user` / `manager`) using the pending
approval set plus the existing assignee/manager lookup. Rendering
groups them under labelled sub-headings in priority order (board
first — the operator is the bottleneck there), each with its own
awaiting-text so the row's status is unambiguous at a glance.
The component accepts approvals via prop; the caller passes the
existing `pendingApprovals` it already holds. Translations for both
locales carry the new group labels.
Onboarding was a 7-step linear wizard, same information hierarchy as
Settings but behaving like a modal. Replace with the SettingsShell tab
pattern: one row of six tabs (nodecheck / providers / company /
modelpool / executionmode / mission), free navigation, per-tab
completion ticks derived from the wizard's own validation rules.
Sub-fixes on the same pass:
- ActionButton: add whitespace-nowrap + shrink-0 so "选择" never
stacks vertically on narrow containers. Global — every caller gets
the correct behavior
- CompanyStep: CEO Runtime Provider/Model go vertical (space-y-3
instead of grid-cols-2) so long names like "Fireworks AI" /
"Kimi K2.5 Turbo Preview 0911" stay readable; workspace-path Input
wrapped in min-w-0 flex-1 container so it yields, not the button
- MarkdownBody: render react-markdown in chat bubbles (was plain text)
i18n: added onboarding.stepsAria for the tablist label.
Drop code that nothing consumed. Verified by full renderer+shared grep:
every removed channel had zero callers via window.agentCompany.* and
zero references from desktop-api types. No backend internal use either —
the database/API-server layers keep their methods, only the IPC exposure
goes.
Removed IPC handlers (26 total):
Thread CRUD surface (12) — writes happen inside the backend
createThread, listThreads, addThreadEvent,
setThreadWaitState, resolveThreadWaitState,
addParticipant, removeParticipant, listParticipants,
subscribeThread, unsubscribeThread,
listSubscriptions, listSubscribedThreads
(kept reads: getThread, listThreadEvents, listThreadWaitStates)
Half-implemented features (13)
saveSecret, listComments, getAgentMetrics, getApiPort,
listWakeupRequests, listAgentMessages, autoAssignTask,
listRunEvents, recoveryDismiss,
getHeartbeatPolicy, setHeartbeatPolicy,
getRuntimeState, setRuntimeState
Whole vertical slice: openConnectorAuthTerminal
IPC handler + channel + preload + DesktopApi type +
connector-auth-service function + useConnectorCallbacks
handleLoginConnector + OnboardingContext.onLoginConnector +
OnboardingWizard wiring. Pi-mono has no CLI auth flow;
the no-op that "returned false for legacy UI compatibility"
had no legacy UI left.
Orphaned modules (zero importers in repo):
src/main/runtime-env.ts
src/main/database-stale-recovery.ts
Dead schema files (only referenced by their own barrel re-export):
schemas-participants, schemas-subscriptions, schemas-heartbeat,
schemas-run-events, schemas-runtime-state, schemas-wakeup
Renderer leftovers:
.activity-row-enter CSS + its two unreferenced keyframes
5 orphan i18n keys (app.console, app.refresh, app.expand,
activity.timeline, auto.trigger) left over from earlier feature
deletions
Also update two unit tests that mocked openConnectorAuthTerminal in
the RegisterCoreHandlersDependencies pick.
"删除 OrbitOps AI?" was being truncated to "删除O..." inside the
~100px inline confirm row, because the row has to share width with
the 是 / 否 buttons. The name-interpolation was the wrong half of
the message to keep: the user has just clicked the trash icon on a
specific row, so **which** company is about to be deleted is
already anchored by the row they are looking at. The bare
confirmation "删除?" / "Delete?" fits comfortably and loses zero
information.
- zh key: "删除{name}?" → "删除?"
- en key: "Delete {name}?" → "Delete?"
- Removed the `.replace("{name}", company.name)` call and the
fallback string that also interpolated `company.name`.
No visual change to the 是 / 否 buttons; the confirm still blocks
the row, still requires an explicit click. The only thing gone is
the truncated company-name echo.
Two subtractions surfaced by the fresh Jony+Jobs pass on the Dashboard:
- The "Company status line" at the top of the dashboard showed three
counters: `{activeAgents} agents active · {activeTasks} tasks in
progress · {completed} completed`. The 4-card MetricsGrid directly
below it already rendered the same agent count and active-tasks
count as its primary tiles. Seeing the same number in vertical
succession is the cheapest way to tell users "we weren't sure
what to show." Deleted the whole status-line block + the companyName
prop + the now-unused caller wiring in OperationsSections.
- The Active Work list rendered a `animate-ping` halo on the first
two running runs (`i < 2`) and skipped it for the rest. Arbitrary —
either the ping is a meaningful "I'm active" signal for every
running run, or it is decoration. Dropped the ping entirely; the
solid green dot on the robot icon is already enough to say
"running," and losing the double-layered animation lets the
dashboard feel calm instead of twitchy.
Dead i18n keys removed alongside (zh + en):
dash.agentActive / dash.agentsActive / dash.taskInProgress /
dash.tasksInProgress / dash.completedCount.
Typecheck clean.
The Edit modal was still a vertical scroll of 5 stacked sections (12+
form fields + an embedded Heartbeat card + action bar) even after C5
named the sections. A modal is a transient affordance — making the
user scroll inside it was the wrong design. Same IA move we made for
the Settings page one round ago: add tabs.
Tabs inside the modal:
Identity | Runtime | Budget & scope | Behavior
Each tab swaps the pane. Tab label carries the section context, so
every section body lost its SectionHeader wrapper — the nested
"card-in-a-card" look around HeartbeatSection is gone; Behavior tab
just renders the heartbeat controls directly.
Field-level polish bundled in the same pass because it hit the same
render paths:
- Identity tab: Name + Role paired in a 2-column grid; Department +
Reports To paired. Title spans full width because its values are
often longer. The modal no longer grows tall just to fit two short
inputs side by side.
- Runtime tab: Status stands alone at the top (high-frequency
control — pause/resume). Connector + Model paired; Thinking Effort
+ Workspace paired. Each paired row keeps its inline hint (connector
issue / effort-not-supported) tied to the field it belongs to,
instead of appearing as loose paragraphs between fields.
- Budget tab: Budget input + BudgetBar + burn line cluster into one
grouped column so typing a new cap updates the visual context in
place.
- Behavior tab: HeartbeatSection rendered at the tab root — no
SectionHeader wrapper around it. The card's "Continuous Execution"
title + Wake-now button now read as the primary content of the
tab, not as something buried beneath a header.
Also removed:
- `<input type="hidden" data-agent-id={agent.id} />` in RuntimeSection
— dead code, zero semantic purpose, leftover from an earlier
experiment. Caught during the audit.
- `SectionHeader.tsx` — no more callers after sections stopped
wrapping themselves in it. File deleted.
Added i18n keys (zh + en):
agent.form.tabsAria.
Tab-label copy reuses the existing `agent.form.sectionIdentity`,
`sectionRuntime`, `sectionConstraints`, `sectionBehavior` keys —
same strings that the removed SectionHeader rendered, now carried
by the tab labels.
Covers EJ1 / EJ2 / EJ3 / EJ4 / EJ5 / ES1 / ES3 / ES4 from the
second-round Edit-modal audit.
The settings page was a single vertical scroll containing seven
unrelated concerns. Users had to scroll past theme preferences to
reach Providers — which is the most-touched surface — and mode +
secrets + shortcuts lived below that. There was no signal about
where anything was.
Introduced `SettingsShell` with a horizontal tab row along the top:
General | Execution mode | Providers | Shortcuts | Data | Secrets
Each tab swaps the content pane. Active tab is visually anchored
with a 2px primary underline + foreground text; inactive tabs stay
muted with a hover promotion. All tabs are real `role="tab"` +
`aria-selected` so screen readers announce the current pane; the
nav has `aria-label="Settings categories"`.
Tab state is local — settings are ephemeral navigation, no reason
to persist across company switches or app restarts. Default is
"general" because that's where first-time users land.
Refactor consequences:
- `OperationsSections.tsx` now renders `<SettingsShell />` + the
OAuth prompt modal portal; the old double-Panel wrapper with
"Profile and release" + "Providers" titles is gone. The "Profile
and release" label always fought with what was actually inside
the pane — Jobs audit item S-S2 addressed.
- Providers moved from a separate sibling Panel into the main IA.
Previously Providers came AFTER a 7-section scroll; now it is a
peer of General / Mode / etc., reachable in one click.
- Deleted the now-unused `SettingsPane.tsx` orchestrator. Its
single-column rendering survived only as a composition step that
SettingsShell replaces.
Added i18n keys (zh + en):
settings.tabs.general / mode / providers / shortcuts / data /
secrets; settings.tabsAria.
Covers J-S2 / J-S7 / S-S5 / S-S7 from the settings audit.
The previous SettingsPane.tsx hid 8 unrelated concerns behind 73 "lines"
of JSX where each line ran 400-2000 chars. Reading the file was
infeasible; editing it meant scrolling horizontally through a single
statement spanning every button, modal, and table row.
Split by concern into `settings-sections/`:
SettingsPane.tsx 62 LOC orchestrator
GeneralSection.tsx 94 LOC theme / language / auto-update
ExecutionModeSection.tsx 105 LOC AI CEO ↔ AI Co-Founder toggle
ShortcutsSection.tsx 40 LOC keyboard reference (read-only)
DataSection.tsx 123 LOC update check / backup / restore /
secret backend
SecretsSection.tsx 102 LOC per-company secret bindings
Every file now reads top-to-bottom with normal indentation.
Behavioral changes bundled (they belong with the restructure, not a
separate pass):
- Preferences save model unified: theme already auto-saved on change;
language and auto-update checkbox now do the same. The global
"Save settings" button is gone — there is no longer a state where
a toggled language sits unpersisted waiting for a second click.
- Execution mode card gained a subheader line that names the current
company ("Applies to current company: X"). Multi-company users can
no longer confuse which company they just flipped.
- Auto-updater checkbox gained an inline hint: "Check for application
updates at launch" — the previous naked checkbox never explained
what happens when it is off.
- Restore profile moved into DataSection's backup/restore group,
given `tone="danger"`, and gated behind a `window.confirm` with
copy warning about running work being lost. Backup stays neutral.
The previous layout put Restore in the same 2-col grid as Save +
Check Updates + Backup, making a profile-replacing operation look
as routine as saving a theme preference.
- Secret backend display promoted from a naked text line to a
bordered card so it matches the surrounding section rhythm.
Added i18n keys (zh + en):
settings.updaterHint / settings.modeScope / settings.backupRestore /
settings.restoreConfirm / settings.restoreHint.
Covers J-S1 / J-S6 / S-S1 / S-S2 / S-S3 / S-S4 from the settings audit.
Remaining work (tab sidebar, Providers IA reordering, copy sweep)
lands in follow-up commits.
Per user request: the "显示控制台 / Show console" button in the top-right
header and the bottom drawer it opened are no longer needed — the Runs
section already renders the same log tail in-page via RunDetail, so the
drawer was a duplicate surface. Deleted the entire feature instead of
leaving a hidden toggle that attracts bugs.
Removed files:
- src/renderer/views/ConsoleView.tsx (126 LOC)
Removed renderer integration points:
- App.tsx: header "Show/Hide console" button, ConsoleView render,
setConsoleOpen(true) call after startRun, TerminalWindow icon import
- views/sections/ExecutionSections.tsx: onOpenConsole callback,
setConsoleOpen subscription
- components/runs/RunCenter.tsx: onOpenConsole prop (removed from
forwarding chain)
- components/runs/RunDetail.tsx: onOpenConsole prop + the Lightning
"Open console" ActionButton. The cancel-run button remains as the
sole Panel action now that the console entry point is gone.
- hooks/useUIController.ts: consoleOpen / setConsoleOpen subscription
- hooks/useDesktopEvents.ts: "toggle-console" shortcut dispatcher
- state/slices/navigation.ts: consoleOpen field, setConsoleOpen,
toggleConsole actions, and the two auto-open-on-navigate triggers
that used to flip the drawer when jumping to a run
Removed main-process integration:
- main/desktop-shell-menu.ts: "Toggle Console" menu item + CmdOrCtrl+`
accelerator
- main/desktop-shell-window.ts: onShortcut union type narrowed
- shared/types/events.ts: ShortcutEvent action union narrowed
Removed i18n keys (zh + en):
- app.hideConsole / app.showConsole
- console.title / console.noRuns / console.close
- common.openLog / common.noRunSelected / common.waitingForLog
- runs.openConsole
Log capture itself (logBufferRef in useCompanyController + run-log
events) is unchanged — the Runs section still displays it. Nothing
was moved to a "disabled but present" state; the feature is out of
the tree entirely. Typecheck and lint pass.
Stragglers from the Communication audit that did not fit into C10's
FilterBar scope:
- ChannelHeader carried two hardcoded English strings: the sidebar
reveal button's aria-label ("Show sidebar") and the "Open task"
link that appears on task-discussion channels. Both now go through
i18n — added `comm.showSidebar` ("显示侧边栏" / "Show sidebar") and
reused the existing `comm.openTask` key. Users running in Chinese
now get a fully Chinese header.
- MessageBubble had two 9px uppercase chips (department badge and
priority badge). 9px with tracking is unreadable at standard display
density; bumped both to 10px and loosened the tracking slightly so
the letters don't collide. Same typography decision we made for
the OrgChart connector label in C3.
Covers the remaining Communication audit items. J-C1 (resizable
sidebar) and J-C6 (resizable ThreadPanel) are feature additions and
stay out of scope per the audit's "bug fix only" contract.
Two S-level audit items landed together because they are the same
"first-second orientation" concern on the team page:
S1 — Empty OrgChart was the first thing the board saw; the canvas
carried no summary data. Now a compact summary strip sits above
the chart showing Members / Running / Attention / Pending hires /
This-month budget (spent/cap/%). All five numbers computed from
the existing snapshot — no extra IPC.
S9 — Activity and budget context previously required visiting three
separate pages. The summary strip collapses the minimum-useful
subset into a single glance; the full drill-down paths (Work,
Approvals, Costs) remain one click away.
S10 — The `orgchart` SectionId was a dead rendering branch:
- Not in the sidebar (useUIController sidebarGroups)
- Not in the command palette (paletteOnlyNavDefs)
- Not reachable from any navigateTo() call in renderer code
The only callers were an automation post-hire notification and a
unit test — both pointed at "orgchart" as a navigation target.
Routed both to "agents" instead (which renders the OrgChart at
the top anyway, plus the new summary strip and the roster), so
clicking the "Agent Hired" notification now lands on the team
page with the new hire highlighted.
Removed:
- `orgchart` from SectionId enum (src/shared/types/enums.ts)
- `section === "orgchart"` render branch (OrganizationSections.tsx)
- `nav.orgchart` i18n key (zh + en)
Added:
- src/renderer/components/organization/TeamHeaderSummary.tsx
- team.summary.* i18n keys (zh + en)
Covers S1 / S9 / S10.
Jobs audit item S11: "智能体名册" (Intelligent-agent roster) reads as
machine-generated in a UI whose left navigation already uses the human
word "团队" (Team). The collision between the technical term and the
domain term made the team page feel less real than it is — it is a page
about running an AI company whose members happen to be agents, so the
label should match the nav.
- `org.agentRoster` "智能体名册" → "团队成员"
- `org.agentRoster` (en) "Agent roster" → "Team members"
- `org.noAgentsYet` "暂无已招聘的智能体" → "还没有团队成员"
- `org.noAgentsYet` (en) "No agents hired yet" → "No team members yet"
- `org.noAgentsDetail` (en) "Hired agents..." → "Hired members..."
- `org.hireRequestNote` Reworded to use "新成员" + tighter flow
- `agent.form.pendingReadOnly` "编辑智能体资料" → "编辑成员资料"
Technical contexts where "智能体" is the subject matter (skills injection
hint, workload charts, run tables, etc.) are left untouched — the word
is the product's central noun and replacing it everywhere would harm
clarity for users reading technical copy.
Also dropped the dead `org.fit` / "Fit" key — replaced by `org.fitChart`
in commit 50a7196 when the Fit button became an icon. Zero orphaned
callers (verified by grep).
Before this commit one piece of state (`selectedAgentId`) did three
jobs: roster highlight, navigation destination, and Edit-modal open
flag. The three jobs had incompatible lifecycles — highlight should
persist across modal close, navigation should land on a screen
without opening a modal, and the modal itself only wants to open on
explicit intent. The conflation was the root cause of the "Edit: CEO
auto-opens on first Team view" bug (commit 41132e2 patched the most
visible symptom by not pre-selecting the lead agent, but the coupling
remained).
Split into two slice fields:
- `selectedAgentId` — which agent is currently highlighted in the
roster / OrgChart. Survives modal close. Set by: row click, card
click, navigation from Inbox / Hiring (all of which pass through
`openAgentEditor` which sets both so the highlight follows the last
interaction).
- `editingAgentId` — which agent's Edit modal is open, or
NEW_ENTITY_ID for the "Request new hire" modal. Only set by
explicit user action; cleared on modal close. Never set by
navigation or onboarding — the pre-select pathway cannot
accidentally open a modal again.
Plumbing:
- `selection.ts`: new `editingAgentId` + `setEditingAgentId`, plus
clear on `clearCompanyScopedSelections`.
- `useCompanyData.ts`: derive `editingAgent` record from the id.
- `useCompanyController.ts`: forward `editingAgent` on the section
context.
- `OrganizationSections.tsx`: `agents` section modals read
`editingAgentId` / `editingAgent`. "Request new hire" button uses
`setEditingAgentId(NEW_ENTITY_ID)`. Single-click handlers go
through `openAgentEditor(id)` which updates both states.
Also improved Edit modal title (was "Edit: CEO", a machined English
prefix on a Chinese UI with zero context about the role):
- zh: "编辑 {name}({role})"
- en: "Edit {name} ({role})"
Renders as "编辑 CEO(首席执行官)" in the screenshot example.
Covers J9 / S2.
Previously the agent Edit modal was a 351-LOC wall of 17 stacked form
fields with no visual grouping, mixed header styles between the
skills heading and the field labels, a Save button that was always
enabled, no Cancel affordance, and a Thinking Effort field that
silently disappeared when the selected connector did not support it.
This commit restructures the edit surface into five named sections
— Identity, Runtime, Budget & scope, Behavior, Skills — each with a
consistent SectionHeader (11px uppercase muted, border-top divider).
Every section lives in its own ≤150-LOC file under
agent-form-fields/edit-sections/ so nothing stays god-sized:
EditFormBody.tsx 199 LOC (orchestrator: isDirty, handlers)
IdentitySection.tsx 74 LOC
RuntimeSection.tsx 114 LOC
ConstraintsSection.tsx 58 LOC
SkillsSection.tsx 36 LOC
EditActionBar.tsx 87 LOC
SectionHeader.tsx 18 LOC
Behavioral changes:
- Save is disabled until the form is dirty (trimmed-string + numeric
budget comparison against the persisted agent record). The board
sees whether changes are captured without clicking Save to find out.
- Cancel button added, wired through AgentForm → AgentEditor →
OrganizationSections, which clears selectedAgentId (same as the
modal's close X — a proper bottom action avoids users reaching for
the corner).
- Thinking Effort is always shown. When the connector provider does
not accept the hint the Select is disabled with an inline note,
so the option is discoverable instead of silently hidden.
- Budget shows a live BudgetBar + "Spent $X of $Y" line when the
budget or spend is non-zero. The raw number input on its own left
burn state invisible.
- Capabilities placeholder replaced with a concrete example
("writes React components, reviews PRs, ...") — the previous
"What this agent does" was a circular prompt.
- OrgChart role bar went from three tiers (CEO 4px primary /
manager 3px warning / IC 2px muted 30%) to binary (root 4px
primary / everyone else 2px border). Orange-warning for managers
read as an alert, not a rank; managerial position is already
encoded in the tree's topology.
Also added i18n keys (zh + en):
agent.form.sectionIdentity / sectionRuntime / sectionConstraints /
sectionBehavior / effortNotSupported / budgetBurn; updated
capabilitiesPlaceholder copy.
Covers J2 / J7 / J8 / S5 / S6 / S7 / S8.
Two audit items addressed together because they share the same org-page
render surface and both enforce "read the screen correctly before acting":
1) Status dot colors in OrgChart were undocumented — users saw five
distinct colors (running/active/idle/paused/error/terminated/
pending_approval) with no key. Added a compact top-left legend that
only lists the statuses actually present in the current org, so a
healthy all-running company shows two rows, not seven. Label copy
reuses existing agent.form.status* keys (zh/en).
2) The agent Edit modal offered Delete and Status=terminated on every
agent, including the root (no reportsTo) — the CEO. Deleting or
terminating a root orphans every direct report. Added `isRootAgent`
check in EditFormBody:
- Delete button: disabled, with `rootDeleteBlocked` inline note
telling the user to reassign reports first.
- Status select: `terminated` option hidden; `rootTerminateBlocked`
note explains why.
Scope note: this is UI-level guardrail only. The deleteAgent IPC /
API route is unchanged — same-company power users with DB access can
still do surgical fixes; the board-facing UX just refuses to be a
footgun. Covers J5 / S3 / S4.
Onboarding ProvidersStep now reads the 26-entry CONNECTOR_CATALOG and
renders featured connectors (claude-pro, openai-codex, anthropic, openai,
amazon-bedrock) up front with a collapsible advanced list for the
remaining 21. Each auth kind gets a dedicated card component: OAuth
button flow, inline API-key form, and a cloud deep-link to Settings.
Delete legacy Claude/Codex/Gemini hardcoded cards and the compatible-API
slot rotation. Swap ModelManagement Settings panel for the new
ProvidersPanel driven by credentialsList IPC.
Adds ~40 automation.ruleEditor.* and automation.workflowEditor.* keys
to en.ts + zh.ts (labels, placeholders, error messages, action labels).
RuleFormModal and WorkflowFormModal now route every hardcoded English
string through useT(), including validation errors and modal titles.
Closes remaining Z-B i18n work after the prompt-builder locale
dispatch (cf92e0c), dept-keywords typing (a78bdb9), and sidebar/
formatters/notification/skills i18n landed earlier.
SkillsSection had nine hardcoded English strings (header, filter
placeholder/aria-label, empty-state copy, button labels). Move them to
skills.* keys in en.ts/zh.ts and swap the component to useT().
Missing keys now log a console.warn in dev; at runtime the default-locale
dict is consulted before falling back to the raw key so zh users are not
shown literal keys when a zh translation is absent.
Replace options IIFE with memoised runSelectOptions; extract magic-string
run-summary filters to SHUTDOWN_SUMMARY/WORKER_CRASH_SUMMARY constants;
localise Close console aria-label via console.close key.
The mass find-and-replace incorrectly replaced the word 'surface' in
human-readable text strings and type literals, not just CSS class contexts.
Fixed: App.tsx control surface text, DocumentCenter knowledge base text,
i18n en.ts, and Panel tone prop type literal.
- Replace all bg-[color:var(--old)] with bg-background/bg-card/bg-muted/bg-primary etc.
- Replace all text-[color:var(--old)] with text-foreground/text-muted-foreground/text-primary etc.
- Replace all border-[color:var(--line)] with border-border
- Replace all inline var(--old) with var(--new) equivalents
- Replace all rounded-[6px]/rounded-[8px] with rounded-lg
- Replace .surface class with bg-card border border-border shadow-sm
- Replace .focus-ring class with focus-visible Tailwind utilities
- Replace .mono class with font-mono
- Replace data-theme attribute with class-based dark mode
- 99 files updated, ~1265 lines changed, zero old references remaining
- Add "skills" to SectionId union in shared types
- Add SkillRecord, SkillImportResult, SkillUpdateStatus types to shared/types.ts
- Create SkillSlice with skills list, selection, file path, and import modal state
- Wire SkillSlice into Zustand store composition
- Add "nav.skills" i18n keys for English and Chinese
- Add Skills sidebar nav entry with PuzzlePiece icon between Communication and Documents
- Add section === "skills" rendering route in App.tsx
- Clear skill state on company switch
- Add skills case to navigateTo in NavigationSlice
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add deliverable review gate: pause workflow when agents produce important
documents (PRDs, design docs, etc.) for human review before continuing
- Add company-level reviewDeliverables setting with onboarding card selector
- Block agent heartbeats when deliverable review is pending (agent + CEO)
- Trigger review at document creation time in API server for reliability
- Fix autoApproveHires vs reviewDeliverables conflict in approval-policy
- Fix app quit: force-exit on all platforms (macOS Dock, Windows tray, Linux)
- Add multi-platform CI/CD pipeline (macOS arm64/x64, Windows x64/arm64,
Linux x64/arm64 with deb/AppImage/rpm/snap) with code signing support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove 6 group headers and reduce navigation to: Dashboard, Agents,
Work, Messages, Documents, Approvals, Runs, Social Media, Settings.
Absorbed sections remain accessible via command palette.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace all hardcoded Tailwind colors with CSS custom properties (--warn, --warn-soft, --text, --muted, --panel-soft, --success, --danger, --muted-strong)
- Use rounded-[10px] instead of rounded-lg for design system consistency
- Add aria-label on dismiss button for accessibility
- Update NeedsAttention label to 'Needs Your Attention' in en.ts and zh.ts