Skip to content

Latest commit

 

History

History
138 lines (112 loc) · 37 KB

File metadata and controls

138 lines (112 loc) · 37 KB

AGENT NOTES

Styling Guidelines

  • 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-account container 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; only default may 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 in styles/components/provider-accounts.css, backend policy in provider-accounts/, with limitations and isolated validation in dev-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.css if a shared pattern is needed.
  • Keep aggregate entry files (e.g., src/styles/controls.css, messaging.css, panels.css) lean—they should only @import feature-specific subfiles located inside src/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 from src/styles/components/window.css for 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.tsx with shared window chrome. Transient popups such as transcript filters dismiss on outside interaction and have no close button.
  • Authentication recovery uses lib/auth-recovery.ts and components/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 in styles/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-toggle with aria-expanded/aria-controls. Keep search state scoped to its instance/session and close it when that view becomes inactive.
  • The composer reserves /btw for native session.generate, outside ordinary prompt/command submission. Its ephemeral question/answer window uses DismissibleWindow and styles/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 via interruptionFocus; 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 in styles/components/interruption-dock.css, with a token-based accent header and fixed action footer outside the scrolling request content. See dev-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-permission shield 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.tsx outside 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 in styles/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.ts bounds 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 in styles/messaging/tool-call/question.css.
  • Pending-request recovery uses the authenticated broker in packages/server/src/server/routes/pending-requests.ts when 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. See dev-docs/SESSION_HISTORY_QUERIES.md for scope, snapshot and cleanup semantics.
  • Keep agent, model, and thinking controls in the composer footer via PromptContextControls; adapt that footer with the named prompt-composer container 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 in styles/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.ts observes the conversation and its intersection with visualViewport, 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 in window-state.ts; Tauri uses window_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 share prompt-input/useDeviceAttachments.ts for serialized budgets and epoch-based draft/focus fencing; keep lifecycle and read logic out of prompt-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 @query token. The default prompt placeholder lists skills alongside files and agents in every locale. Keep the composer free of a permanent Skills button or /skills shortcut. Explicit attachments use native skill.list for the session Location and send IDs through prompt.skills; keep selection lifecycle in prompt-input/SkillAttachments.tsx and styling in styles/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.css geometry), 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-radius is an alias). Register badge variants in styles/components/badges.css; use .badge-shape for utility-styled labels rather than adding a local radius.
  • The message-content popup and Chat settings share components/transcript-visibility.ts and the semantic icons from components/message-content-icons.ts; tool presentation metadata lives independently of renderers in components/tool-call/tool-presentation.ts. Popup styles live in styles/components/transcript-filters.css.
  • Tool-result images retain native state.content and render through the shared components/tool-call/output-images.tsx surface, including MCP and specialized tools. Keep image bytes out of text projections (copy/search/speech); image styles live in styles/messaging/tool-call/images.css.
  • Code Mode reads native metadata.toolCalls in tool-call/renderers/execute-data.ts; keep call-list styling in styles/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 in styles/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.ts for 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 in styles/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 the session-center container 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 in styles/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 through stores/timeline-previews.ts; never mount whole message/tool cards or fetch transcript windows for hover. Keep the viewport-bounded surface in styles/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 in dev-docs/PALETTE_SOURCES.md. Keep selection independent of participant identity, and keep transcript/composer surfaces distinct. Run palette-quality.test.ts and 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-settings container. Swatch styles live in styles/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-overlay for 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.tsx and styles/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).

Coding Principles

  • 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 FileRowActions with a pinned eye using the shared icon-toggle active state; secondary actions use whole-row overflow measurements. Keep selection beneath the entire row including controls and rollover in styles/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 in styles/panels/git-history.css, row actions in styles/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.tsx chooses 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. See dev-docs/FILES_PANEL.md for 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. Run node scripts/test-tauri-input-deadlock.mjs --baseline on Windows when changing it. See dev-docs/TAURI_WINDOWS_INPUT_DEADLOCK.md for 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. Run node scripts/test-tauri-macos-window.mjs --baseline in a graphical macOS session and retain the ARM64 CI regression. See dev-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 upgrade through opencode-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 isolated scripts/test-opencode-upgrade-native.mjs fixture.

  • 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-availability instruction 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 with scripts/test-git-degraded-native.mjs using 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.ts and 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/opencode tree 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 in opencode-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: native location.reload rebuilds 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. See dev-docs/OPENCODE_V2_POST_BETA.md for version boundaries and isolated validation evidence.

  • A selected CLI's service status can report stopped for a live older daemon. Preserve the bounded, read-only registration fallback in workspaces/native-service-registration.ts and 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's config.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. See dev-docs/SESSION_ENVIRONMENT.md.

  • One bundled codenomad.automation V2 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. See dev-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.get global discovery directory, including reconnects. Never derive a running daemon's roots from backend/startup environment or CLI debug 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.md for 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 shared session.active probes 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 / ownsLocation with the event purpose). 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.ts and the isolated native worktree fixture.

  • Worktree discovery/create/remove use workspaces/native-worktrees.ts and the native OpenCode worktree API. CodeNomad supplies the .codenomad/worktrees default, 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 through scripts/test-opencode-location-native.mjs with an isolated CLI and tests/browser/worktrees.test.ts for 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.worktreesChanged refreshes 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/; see dev-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.snapshot for 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 candidate location.get, ordinary queue GETs, debug inventory or the unreleased /api/location/pending API. Validate origin, full coverage and every placement before connection-scoped capability admission; only bounded declared native rpc.unavailable/rpc.method_not_found errors permit unsupported fallback. Other failures retain queues non-authoritatively. Preserve compaction admission, deletion/connection fences, global Forms and settlement tombstones. See dev-docs/OPENCODE_V2_COMPATIBILITY.md for 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.

Multi-Language Support (i18n)

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();) and tGlobal(...) in stores/non-component code.
    • Implementation: packages/ui/src/lib/i18n/index.tsx
  • Where messages live: packages/ui/src/lib/i18n/messages/<locale>/ as TypeScript objects ("flat.dot.keys": "string").
    • Each locale has an index.ts that merges message parts; duplicate keys throw at build time.
    • Merge helper: packages/ui/src/lib/i18n/messages/merge.ts
  • Adding a new string: add it to the appropriate .../messages/en/*.ts part 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.other and choose in code.
  • Adding a new language: add a new messages/<locale>/ folder + index.ts, register it in packages/ui/src/lib/i18n/index.tsx, and add it to the language picker in packages/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.json is migration input only).
  • Avoid English-only paths: do not import enMessages directly in feature code; always go through t(...) so locale changes apply.

File Length Guidelines (Highlight Only)

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.

Tooling Preferences

  • Use the edit tool for modifying existing files; prefer it over other editing methods.
  • Use the write tool only when creating new files from scratch.
  • Browser rendering regressions live in packages/ui/tests/browser/, with deterministic HTTP fixtures beside them in fixtures/. 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(...) in src/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.tsx computes 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/ui after npx playwright install chromium. CODENOMAD_BROWSER_PATH optionally selects an existing Chromium executable; it does not target the installed application or user sessions.

V2 Runtime Launch

  • 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.

Commit Message Guidelines

  • 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.