- Provider accounts use a native dropdown like Web search defaults, without an Accounts disclosure or selectable account rows. Provider headers keep the name and text Manage models button on one row; a faint separator distinguishes the account controls below. Account actions are always visible, not hover-revealed. The
provider-accountcontainer stacks the label above the dropdown/actions at narrow card widths. Native account ordering supplies the current account; rename/remove drafts stay keyed by credential. Environment connections are read-only. The browser never reads credential secrets. Native labels win; onlydefaultmay display a sanitized Codex login from the owned server adapter. Opt-in Codex rotation runs before prompt/command admission, using fresh bounded quotas, local manual-change fences and no mutation/prompt replay; other providers remain manual. Account styling lives instyles/components/provider-accounts.css, backend policy inprovider-accounts/, with limitations and isolated validation indev-docs/PROVIDER_ACCOUNTS_UX_PLAN.md. Browser preview percentages remain simulated. - Reuse the existing token & utility layers before introducing new CSS variables or custom properties. Extend
src/styles/tokens.css/src/styles/utilities.cssif a shared pattern is needed. - Keep aggregate entry files (e.g.,
src/styles/controls.css,messaging.css,panels.css) lean—they should only@importfeature-specific subfiles located insidesrc/styles/{components|messaging|panels}. - When adding new component styles, place them beside their peers in the scoped subdirectory (e.g.,
src/styles/messaging/new-part.css) and import them from the corresponding aggregator file. - Prefer smaller, focused style files (≈150 lines or less) over large monoliths. Split by component or feature area if a file grows beyond that size.
- Co-locate reusable UI patterns (buttons, selectors, dropdowns, etc.) under
src/styles/components/and avoid redefining the same utility classes elsewhere. - Use the shared
.window-*primitives fromsrc/styles/components/window.cssfor dialog, popover, and floating-window headers, toolbars, bodies, footers, titles, and actions. - Persistent utility windows and transcript-covering previews expose an upper-right close button in their existing top toolbar; do not add a dedicated row just for dismissal. Use
components/window-close-button.tsxwith shared window chrome. Transient popups such as transcript filters dismiss on outside interaction and have no close button. - Authentication recovery uses
lib/auth-recovery.tsandcomponents/auth-recovery-dialog.tsx: confirm CodeNomad auth status before showing an expired-login form, reconnect in place to preserve drafts, and never replay failed mutations. Its shared window styles live instyles/components/auth-recovery.css. - Persistent command/search utility windows use
components/dismissible-window.tsx: non-modal, no scrim, outside interactions keep them open, and explicit toggles use.icon-togglewitharia-expanded/aria-controls. Keep search state scoped to its instance/session and close it when that view becomes inactive. - The composer reserves
/btwfor nativesession.generate, outside ordinary prompt/command submission. Its ephemeral question/answer window usesDismissibleWindowandstyles/components/session-aside.css; cancellation and inactive/session transitions fence late results without interrupting the main session. - Native questions and permissions are answered in the shell-owned
InterruptionDock, slotted above the composer. Pending questions appear only in the dock; the transcript retains native completed question results. Badges target the dock viainterruptionFocus; omit transcript-source and answer-in-dock links. Keep selection stable by request kind/id across queue refreshes, with bounded previous/next navigation hidden for a single request. Preserve request drafts across refreshes and session navigation. Keep its square, height-bounded chrome instyles/components/interruption-dock.css, with a token-based accent header and fixed action footer outside the scrolling request content. Seedev-docs/NATIVE_INTERRUPTION_UX.md. - Keep disclosure chevrons leading without changing their directions; only unrelated-conversation questions replace the blue question icon with the shared red
session-permissionshield badge (icon only). - Requests from the open conversation and its recursive descendants can expand normally. Other conversations first appear as a compact, explicitly expandable preview; answering stays in place. Keep persistent textual provenance prominent (other conversation, subagent with parent, or sessionless project request), with an explicit View conversation action for session-owned requests. Arrival never replaces the request being edited or navigates sessions. Newly selected external requests after settlement remain compact until explicitly opened. Hidden/inert keyed editors retain drafts, and only an expanded editor compacts the composer. Project badges open the dock in place rather than silently navigating to another conversation.
- Durable permission decisions render through
components/permission-receipts.tsxoutside native tool visibility, with bounded mounted-row reads and a paginated session disclosure for unanchored receipts. Keep receipts out of native message/copy/search/speech projections; styles live instyles/messaging/permission-receipts.css. - An expanded interruption temporarily reduces the composer to its minimum without changing its saved height or draft; resizing resumes after collapse/settlement.
instance/shell/useInterruptionViewport.tsbounds the request/composer stack to the visual viewport during keyboard resize/pan. Keep the dock non-shrinking and its footer reachable on short mobile layouts. - Completed question receipts share native answer decoding with text projection through
tool-call/renderers/question-data.ts. Preserve selected-option descriptions and verbatim free text; keep remaining choices in a native disclosure. Receipt styles live instyles/messaging/tool-call/question.css. - Pending-request recovery uses the authenticated broker in
packages/server/src/server/routes/pending-requests.tswhen the daemon supports loaded-only snapshots. Validate directory ownership before querying and every returned placement before publishing; retain execution-host authority even for cold/empty coverage, including WSL and path aliases. Preserve idle/global Forms, per-kind mutation fences and settled-request tombstones. Errors or incomplete coverage never clear queues. The capability is negotiated per connection, not inferred from a version label; older daemons retain their existing discovery behavior and do not gain the native performance fix merely by updating CodeNomad. - OpenCode settings keep executable selection first and runtime status, install/update and service actions directly inline. Only version details and troubleshooting are collapsed disclosures at the bottom of the runtime panel; log levels remain the final settings card. Troubleshooting always exposes an explicit, confirmed shared-service restart independently of updates; retain the inline activation shortcut after an update, and never downgrade a newer daemon through an older selected CLI. Configuration reload remains a separate process-preserving action. Share controls with the startup recovery dialog rather than routing settings through a separate management modal. Disclosure styles live in
styles/components/opencode-setup.css. - Full-history search/counts use the bundled pruning plugin's bounded queries, outside the transcript store. Current-session results and the global timeline navigate through bounded anchor windows; other-session previews fetch one native message only. Search/results/progress styles live in
styles/components/history-search.css. Seedev-docs/SESSION_HISTORY_QUERIES.mdfor scope, snapshot and cleanup semantics. - Keep agent, model, and thinking controls in the composer footer via
PromptContextControls; adapt that footer with the namedprompt-composercontainer rather than viewport-only breakpoints. - Composer context selectors and actions always share one row on desktop and touch; truncate selector labels rather than wrapping the footer. Dense conversation toolbar/footer controls use
--touch-target-size-compact; timeline markers use the compact control height rather than the global touch target. Toolbar touch spacing lives instyles/panels/session-toolbar-touch.css. Transcript filters join the header action overflow and anchor/focus back to that menu when collapsed. - Composer height limits use the same visible conversation height: minimum 8% (44 px floor), maximum 60% (never below that minimum). Width and draft length do not change the limits.
prompt-input/usePromptViewport.tsobserves the conversation and its intersection withvisualViewport, including keyboard resize/pan. Store manually chosen heights as proportions so the field itself follows viewport changes; migrate legacy pixel choices on the first valid measurement. Viewport-driven changes never overwrite the saved proportion; pointer and keyboard resize controls share the same limits and long drafts remain scrollable. - Browser preview retains its six original fixed templates (responsive, desktop, tablet portrait/landscape, mobile portrait/landscape), without a separate rotation button. Mobile templates automatically use native Android/Chromium touch emulation; iframe fallback changes dimensions only. Profiles share
packages/ui/src/lib/native/browser-emulation.json; only owned native guests may receive device overrides, and returning to dimensions clears them before reloading. Main local/remote desktop content minimums are 390 × 600 CSS pixels: multiply by application zoom, not monitor DPI, and round up native logical dimensions. Restore uses the saved zoom; keyboard/wheel/menu changes update constraints without shrinking larger windows, with monitor-work-area caps at extreme zoom. Electron handles outer-frame offsets inwindow-state.ts; Tauri useswindow_constraints.rs. Preferences and native preview children retain their separate sizing contracts. - Composer attachments use one native-device action and project-scoped
@references. Picker/drop/clipboard bytes shareprompt-input/useDeviceAttachments.tsfor serialized budgets and epoch-based draft/focus fencing; keep lifecycle and read logic out ofprompt-input.tsx. File listing/search validate the session directory through existing workspace ownership before reading a worktree or translated WSL root. - The
@mention menu lists Location skills with search, names and descriptions; selecting one attaches a removable skill badge and drops the@querytoken. The default prompt placeholder lists skills alongside files and agents in every locale. Keep the composer free of a permanent Skills button or/skillsshortcut. Explicit attachments use nativeskill.listfor the session Location and send IDs throughprompt.skills; keep selection lifecycle inprompt-input/SkillAttachments.tsxand styling instyles/components/prompt-skills.css. Queued edits restore removable attachments, never reinsert removed skills from the original payload. - Session rows keep actions inline until their measured title, badges, and controls no longer fit. Keep responsive action styles in
styles/components/session-row-actions.css; hidden inline controls remain measurable but inert, and an open overflow menu stays mounted until dismissal. - Session hierarchy geometry lives in
styles/components/session-tree.css; connector axes follow the parent expander at every depth, including selection mode, RTL and touch layouts. - Session search/filter mode uses flat per-session results with an optional subsession switch; filters, sorting, worktree badges and selection use each result's own identity. Normal browsing retains the session hierarchy.
- Never use rounded corners in UI styling; keep corners square unless the user explicitly requests otherwise for a specific change.
- Explicit round exceptions: Yolo, MCP and plugin activation switches (shared
styles/components/switches.cssgeometry), overlay drawer navigation buttons, and floating message scroll buttons. Other chrome remains square. - Tags and numeric/context/token labels also use rounded geometry via
--chip-radius(--pill-radiusis an alias). Register badge variants instyles/components/badges.css; use.badge-shapefor utility-styled labels rather than adding a local radius. - The message-content popup and Chat settings share
components/transcript-visibility.tsand the semantic icons fromcomponents/message-content-icons.ts; tool presentation metadata lives independently of renderers incomponents/tool-call/tool-presentation.ts. Popup styles live instyles/components/transcript-filters.css. - Tool-result images retain native
state.contentand render through the sharedcomponents/tool-call/output-images.tsxsurface, including MCP and specialized tools. Keep image bytes out of text projections (copy/search/speech); image styles live instyles/messaging/tool-call/images.css. - Code Mode reads native
metadata.toolCallsintool-call/renderers/execute-data.ts; keep call-list styling instyles/messaging/tool-call/execute.css, with native image/output ownership in the shared tool shell. - Native websearch text is parsed conservatively by
tool-call/renderers/websearch-data.ts; unknown/oversized responses use the shared raw renderer. Recognized provider-consent Forms retain the generic schema/answer path, with layout instyles/messaging/tool-call/websearch.css. - Settings → Providers separates Models and Web search into two titled groups. Each starts with a provider selector, Connect action and configured-provider list; Web search then shows its global default and project override. Reuse provider rows, selectors, inputs and action styles, without an API-key disclosure or unrelated card design. Search-only integrations stay out of the Models group. Web search configuration lives entirely here; Status has no websearch configuration section. Read connection metadata while this Settings surface is visible, never credential secret values; keep API-key entry explicit and clear drafts on context changes. Use the authorized Global/Project document operations shared with plugin controls and
opencode/native-setting-document.tsfor targeted JSONC edits; the 2.0.18 client has no websearch config-write API. Preserve unrelated fields, native discovery roots, WSL atomic writes and conflict/connection/deletion fences. Group/search styling lives instyles/components/websearch-settings.css; credentials use native integration methods and remain global. - Session timeline placement spans the transcript and composer via the session-owned mount; keep its rail layout in
styles/messaging/session-timeline-rail.css. Hide the rail and force conversation-header actions into their overflow menu below 420 CSS px of thesession-centercontainer on all platforms. Keep that width-only force separate from measured header density so trial layouts can expand again above the breakpoint; filters anchor and return focus to the visible menu. Docked drawers reserve a minimum conversation width of 390 CSS px before overlaying. The composer worktree selector becomes a compact arrow-only cell below 460 CSS px of that conversation container (not the remaining composer width); keep its accessible label and styles instyles/components/prompt-context-controls.css. Timeline visibility must not depend on measured header density, changing status controls or viewport height; preserve the saved visibility preference. - Timeline geometry uses the cached structural index independently of transcript/excerpt loading. Aggregate tool markers retain the outline's representative tool name so their icon does not fall back to
other; preserve that metadata through projection and persisted-index revalidation. Hover/focus previews render bounded Markdown, prefetch visible-nearby excerpts and prioritize the hovered marker throughstores/timeline-previews.ts; never mount whole message/tool cards or fetch transcript windows for hover. Keep the viewport-bounded surface instyles/messaging/timeline-preview.css. - Document any new styling conventions or directory additions in this file so future changes remain consistent.
- Soft palette families live in
packages/ui/src/lib/soft-color-schemes.ts, with references indev-docs/PALETTE_SOURCES.md. Keep selection independent of participant identity, and keep transcript/composer surfaces distinct. Runpalette-quality.test.tsand inspect real rendered captures when changing palette colors or their token mapping. - Palette settings follow the resolved appearance in Auto mode. Keep the picker, single-row square swatches and trailing actions aligned. Swatch names use tooltips and accessible input labels. At narrow card widths, scroll the swatch strip beside the picker and move actions below via the
palette-settingscontainer. Swatch styles live instyles/components/theme-scheme-swatches.css. - Appearance mode and the saved light/dark selections are independent (
lib/appearance-preferences.ts). Message/tool cards use the muted surface, inset output and the composer use the base canvas, and preferences use the same secondary surface as the main panels. Use--surface-hover-overlayfor a subtle local rollover; preserve selected backgrounds beneath that overlay instead of replacing them with a generic panel color. - Right-panel base-canvas button rollover overrides live in
styles/panels/control-hover.css; do not substitute the secondary surface merely to show hover. - V2 plugin activation controls use the shared rounded SUID switch geometry in
styles/components/switches.css. Present Global and Project as separate per-plugin columns so every mutation has an explicit scope. Cache display snapshots by instance and worktree directory, and refresh them only while the plugin surface is visible. - Keep the Plugins surface minimal: plugin names and Global/Project switches only. Put per-plugin source/status/target details in the name's hover/focus tooltip. Do not add general descriptions, inline explanations, target footers or success commentary. The compact Refresh icon occupies the name-column header and spins during loading and refresh; automatic updates still follow native events, reconnects and visible demand.
- Project and right-panel tabs share
components/tab-scroll.tsxandstyles/components/tab-scroll.css. Keep their native scrollbar above upright content without mirrored transforms, negative border overlaps or permanent compositing hints. Validate shared scrollbar styling and adjoining edges at fractional zoom in the browser and isolated Electron renderer fixtures (tests/browser/tab-chrome.test.ts).
-
Workspace text remains editable in the central file reader (including Markdown source), with Save and Ctrl/Cmd+S. Keep dirty drafts in
components/workspace-file-editor.ts, scoped by instance/directory/path; invalidation must not replace them. Saves reread for external-change confirmation and pass the exact owned directory through the deletion-fenced filesystem write route. Images, binary content and historical diffs remain read-only. -
Files row selection never opens the central preview implicitly. All three modes reuse
FileRowActionswith a pinned eye using the sharedicon-toggleactive state; secondary actions use whole-row overflow measurements. Keep selection beneath the entire row including controls and rollover instyles/panels/control-hover.css. Changes keeps staged/unstaged disclosures with commit actions at the top of the staged section. Git image readers use exact bounded HEAD/index/worktree or parent/commit bytes, not current-file substitutes for historical content. -
The Files panel combines Workspace, Changes and Commits in one compact switch, initially on Workspace. Its lazy worktree tree preserves expanded folders while switching modes; tree styles live in
styles/panels/workspace-tree.css, Git navigation instyles/panels/git-history.css, row actions instyles/panels/file-row-actions.css. Workspace secondary actions are folder-specific (open, terminal, copy path) and file-specific (open, reveal, copy path). Changes rows carry explicit stage/unstage actions and drag between staged/unstaged drop zones. File/diff readers close with a trailing X; the unified/split switch shows the current layout with Columns/Rows iconography. All three modes are cache-first: directory listings, git status and commit history revalidate lazily on filesystem invalidation, mode activation or explicit refresh, never on every focus or expand.components/files-preview-view.tsxchooses the central editable Workspace reader or shared read-only diff reader above the composer. Worktree browsing never checks out a branch or moves a session. Historical reads use bounded worker Git commands behind existing worktree ownership checks. Workspace previews use the bounded directory-authorized preview route; Monaco tokenizers ship locally. Seedev-docs/FILES_PANEL.mdfor lifecycle, migration and validation. -
Tauri's Tao Windows input backport lives in
packages/tauri-app/vendor/; preserve upstream provenance and avoid message pumping under input mutexes. Runnode scripts/test-tauri-input-deadlock.mjs --baselineon Windows when changing it. Seedev-docs/TAURI_WINDOWS_INPUT_DEADLOCK.mdfor the captured failure and override removal criteria. -
Tao's macOS zoom getter must remain read-only: synchronous temporary style-mask changes on borderless windows generate geometry-event feedback and can starve startup navigation. Keep the exact upstream tao#1182 backport beside the Windows fix, with its provenance in
packages/tauri-app/vendor/README.md. Runnode scripts/test-tauri-macos-window.mjs --baselinein a graphical macOS session and retain the ARM64 CI regression. Seedev-docs/TAURI_MACOS_STARTUP.md; do not disable window persistence or change the application chrome as a workaround. -
Verified shared npm OpenCode installations at 2.0.15+ delegate version changes to native
upgradethroughopencode-update/native-upgrade.ts, with bundled npm scoped to the verified prefix. Native Windows image retention replaces the write preflight only on that path; first install, older migration and same-version repair retain direct npm/preflight. Never retry a failed native mutation via npm or restart the daemon implicitly. Validate with the isolatedscripts/test-opencode-upgrade-native.mjsfixture. -
Git is a full-functionality prerequisite, with directory-only degraded conversations when the backend cannot find Git. Only the explicitly opened physical folder is session authority in that mode; never infer sibling worktrees from native project IDs. Keep ancestor/descendant mutation identities covered by the deletion fence across Git availability changes. Inform agents through the owned native
codenomad.git-availabilityinstruction before prompts/custom commands, remove stale context after recovery, and keep this advisory separate from fail-closed environment synchronization. No blocking Git setup UI. Validate withscripts/test-git-degraded-native.mjsusing an isolated CLI/database and provider. -
OpenCode minimum requirements must follow demonstrated technical dependencies, never the latest published or solely tested version. Keep required, recommended/tested and unverified versions distinct in
opencode/runtime-support.tsand setup diagnostics. Validate authenticated daemon metadata/contract before client use/plugin provisioning. Setup uses bundled Node/npm for a shared user npm installation and prefers PATH. The retired private~/.local/share/codenomad/opencodetree must never be discovered or launched; ignore its receipts and saved selections without migrating or deleting it. Keep installer locking and Windows live-executable preflight inopencode-update/installation-lock.ts; register terminal PATH only on explicit installation. A running daemon restart is a separate explicit action. Configuration reload is also explicit: nativelocation.reloadrebuilds every loaded location and cancels pending Forms/permissions, so never use it as an automatic watcher fallback. Retire old wire translations without removing current identity/ownership checks. Seedev-docs/OPENCODE_V2_POST_BETA.mdfor version boundaries and isolated validation evidence. -
A selected CLI's
service statuscan reportstoppedfor a live older daemon. Preserve the bounded, read-only registration fallback inworkspaces/native-service-registration.tsand authenticate historical metadata before allowing start;ensure()repeats discovery. Native service lookup is separate from plugin-root discovery, which must still use the connected daemon'sconfig.get. Never write service registration/configuration files from the backend. -
Profile environment variables are applied server-side before each native session prompt, custom command or session shell request, after ownership and worktree-mutation admission. Build a complete execution-host snapshot with
workspaces/session-environment.ts; never send the profile environment through the browser or skip the per-send write using a cache. Reads and settings edits do not mutate native sessions. Keep native environment failures fail-closed and redact SDK request bodies. Seedev-docs/SESSION_ENVIRONMENT.md. -
One bundled
codenomad.automationV2 plugin uses native discovery and backend presence. All browser/developer tools are available without a Developer Mode toggle; native instrumentation starts with the desktop host. Loading, tool availability and execution targeting are separate: retain the authenticated bridge and session/window fences. Tauri preview children receive no application capabilities; primary-renderer reload must dispose them and final-window checks must count native windows. Seedev-docs/BROWSER_AUTOMATION.md. -
Developer UI tools target the inspected native window/run, not the currently selected project or conversation. Keep backend ownership discovery separate from UI focus; agents must be able to inspect and navigate back from another session themselves. Accessibility refs expire on document/target replacement, not merely on session selection. Browser previews retain their own session attachments.
-
Desktop automation and pruning provisioning follows the authenticated OpenCode connection and its
config.getglobal discovery directory, including reconnects. Never derive a running daemon's roots from backend/startup environment or CLIdebug paths. OpenCode watches the entire discovery root recursively: keep changing presence leases outside it, in the sibling.codenomad/<root-hash>/namespace. Retain existing outside-root storage; migrate inside-root storage while reading older backends' leases without writing there. WSL translates the daemon-reported paths through the selected distro only for filesystem access. Verify heartbeat stability with the isolated native automation fixture. -
Follow
dev-docs/CACHE_REFRESH_CONVENTIONS.mdfor display snapshots, coalesced trailing refreshes, stale-response fencing and authoritative mutation reads. Check existing feature semantics before adding another cache or refresh policy. -
The shared native event relay consumes upstream events before slow routing I/O. Preserve per-session/PTY/Shell and per-recipient FIFO, validated full-location ownership, invalidation/connection/workspace fences and bounded-backlog reconnect recovery. A slow recipient must not block another or the shared SDK subscriber. Validate with the relay regressions and isolated native location fixture.
-
Pending discovery admission lives in
workspaces/pending-discovery.ts. Observe authenticated compaction events before ownership routing; this backend-only state never grants event visibility. Defer legacy full lists and unverified broker reads with non-authoritative 503s before inventory reads and recheck at forwarding. Verified loaded-only capability is connection-scoped. Settlement reconciliation headers require an exact, workspace-bound grant from an authorized mutation attempt and still undergo normal ownership checks. Bounded sharedsession.activeprobes recover missed ends only when the session becomes inactive; failures retain the hold. UI coalesces deferred refreshes without clearing unscanned queues or replaying mutations. Already-admitted reads, already-loaded Locations and wholly unobserved compactions remain mitigation limits. -
Event ownership uses registered-only native worktree snapshots, isolated from display/request discovery scans (
ownsDirectory/ownsLocationwith theeventpurpose). Never run native strategy refresh from routing or join its pending inventory. Keep physical Git identity, nested-folder/WSL projection, native registration, invalidation and disposal checks shared with ordinary ownership; ordinary requests and explicit worktree discovery retain refresh semantics. Regression:instance-event-ownership.test.tsand the isolated native worktree fixture. -
Worktree discovery/create/remove use
workspaces/native-worktrees.tsand the native OpenCode worktree API. CodeNomad supplies the.codenomad/worktreesdefault, named-branch policy and verified family transactions. Git common-directory identity scopes the native inventory to the opened local repository; opaque worktree identifiers are separate from mutable branch labels. Validate throughscripts/test-opencode-location-native.mjswith an isolated CLI andtests/browser/worktrees.test.tsfor selector gestures. -
Worktree inventory snapshots live in
workspaces/worktree-inventory.ts: display reads serve cached data and lazily revalidate, directory authorization uses validated reads, and family transactions force fresh reads. Invalidation retains display data and fences pending scans;workspace.worktreesChangedrefreshes existing UI consumers after a changed snapshot is published. Keep selector opening independent of refresh completion and suppress duplicate selection events during inventory reconciliation. -
Worktree branch/HEAD annotations use one NUL-delimited Git worktree snapshot, intersected with native inventory and verified Git identities; effective checkout roots must respect Git configuration as well as administrative backlinks. Pending move targets belong to the store's instance/family request identity and survive selector navigation/remounts until native reconciliation settles.
-
Session pruning is a narrow V2 plugin/RPC exception under
packages/server/src/opencode/session-pruning/; seedev-docs/SESSION_PRUNING_RPC.md. Bundle it with the shared server for both desktop hosts and provision through normal native plugin discovery. RPC registrations follow backend presence; clean shutdown removes that backend's lease and crashes expire. Loading never deletes content. Deletion occurs only on an explicit pruning request, without an extra enable-write switch or beta-number gate. Keep generic RPC proxy access closed. Writes validate actual storage, a fresh daemon-storage identity challenge and the native durable execution claim inside a synchronous SQLite transaction. Run isolated native concurrency/payload and client-cache regressions; tests must never target the shared daemon or a user's database. -
The same presence-owned bundle exposes only
codenomad.pending-requests.snapshotfor loaded-only Form/Permission recovery. Its private native-service reader is qualified only for native setup version 2.0.22 and bundled Effect 4.0.0-rc.112; this does not raise the global minimum. The authenticated pending broker uses one established owned execution-host root and fixed RPC POST, never candidatelocation.get, ordinary queue GETs, debug inventory or the unreleased/api/location/pendingAPI. Validate origin, full coverage and every placement before connection-scoped capability admission; only bounded declared nativerpc.unavailable/rpc.method_not_founderrors permit unsupported fallback. Other failures retain queues non-authoritatively. Preserve compaction admission, deletion/connection fences, global Forms and settlement tombstones. Seedev-docs/OPENCODE_V2_COMPATIBILITY.mdfor bootstrap warming and snapshot limits. -
Favor KISS by keeping modules narrowly scoped and limiting public APIs to what callers actually need.
-
Uphold DRY: share helpers via dedicated modules before copy/pasting logic across stores, components, or scripts.
-
Enforce single responsibility; split large files when concerns diverge (state, actions, API, events, etc.).
-
Prefer composable primitives (signals, hooks, utilities) over deep inheritance or implicit global state.
-
When adding platform integrations (SSE, IPC, SDK), isolate them in thin adapters that surface typed events/actions.
The UI uses a small custom i18n layer (no ICU/messageformat). When building features, never hardcode user-visible strings.
- Runtime API: use
useI18n()in components (const { t } = useI18n();) andtGlobal(...)in stores/non-component code.- Implementation:
packages/ui/src/lib/i18n/index.tsx
- Implementation:
- Where messages live:
packages/ui/src/lib/i18n/messages/<locale>/as TypeScript objects ("flat.dot.keys": "string").- Each locale has an
index.tsthat merges message parts; duplicate keys throw at build time. - Merge helper:
packages/ui/src/lib/i18n/messages/merge.ts
- Each locale has an
- Adding a new string: add it to the appropriate
.../messages/en/*.tspart file, then add the same key to each other locale’s corresponding file.- Missing translations fall back to English (and finally to the key), so gaps can be easy to miss.
- Interpolation: placeholders are simple
{name}replacements (word characters only). Avoid placeholders like{file-name}. - Pluralization: handle manually via separate keys like
something.one/something.otherand choose in code. - Adding a new language: add a new
messages/<locale>/folder +index.ts, register it inpackages/ui/src/lib/i18n/index.tsx, and add it to the language picker inpackages/ui/src/components/folder-selection-view.tsx. - Locale persistence: the selected locale is stored in app preferences (
locale) and persisted via the server config (default~/.config/codenomad/config.yaml;config.jsonis migration input only). - Avoid English-only paths: do not import
enMessagesdirectly in feature code; always go throught(...)so locale changes apply.
We track file size as a refactoring signal. When you touch or create files, highlight oversized files so the team can plan refactors when time permits.
- Source files: warn after ~500 lines; target limit ~800 lines
- Test files: highlight after ~1000 lines
Behavior for agents:
- Do not refactor solely to satisfy these thresholds.
- When a change touches a file that exceeds the warning/limit, mention it in your final response and include the file path and approximate line count.
- When creating new files, aim to stay under the thresholds unless there's a clear reason.
- Use the
edittool for modifying existing files; prefer it over other editing methods. - Use the
writetool only when creating new files from scratch. - Browser rendering regressions live in
packages/ui/tests/browser/, with deterministic HTTP fixtures beside them infixtures/. Exercise the real Solid components and native event dispatcher rather than reimplementing rendering logic. - Transcript rows retain pointer hit testing during virtualizer scrolling (
styles/messaging/virtual-follow-list.css), so nested code/tool scrollers and message controls receive gestures at their visible target. - Keep the global scrollbar-width default at zero specificity (
:where(...)insrc/index.css) so component visibility rules win. The timeline and transcript use the same standard native scrollbar: only marker rectangles narrow, icons and vertical spacing never shrink.components/timeline-virtual-list.tsxcomputes the timeline's exact extent from measured marker/gap primitives; never estimate hidden/offscreen rows. Manual rail browsing owns its position until an explicit transcript gesture. Native thumb drags retain ownership beyond the gesture timeout and defer window paging until release. Validate with the full stylesheet and real thumb gestures on mixed-height transcripts. - Run them with
npm run test:browser --workspace @codenomad/uiafternpx playwright install chromium.CODENOMAD_BROWSER_PATHoptionally selects an existing Chromium executable; it does not target the installed application or user sessions.
- Native automation instrumentation starts automatically; no Developer Mode toggle or activation restart is required. Do not configure a fixed CDP port or a manual WebView2 profile.
- Rebuild Electron before calling
codenomad.act({ action: "restart" }). For Windows Tauri, stop and relaunch the release executable only when the linker cannot replace it; never stop the shared OpenCode daemon.
- When creating commits, use detailed commit messages: a concise conventional-style subject followed by body paragraphs that explain the user-visible behavior change, the implementation approach, important edge cases or platform considerations, and the validation or test coverage added.
- Prefer messages that explain why the change exists and how regressions are prevented, not just a list of touched files.