diff --git a/.agents/skills/clack-cli-patterns/SKILL.md b/.agents/skills/clack-cli-patterns/SKILL.md index 23e9468ef7..fe55a30792 100644 --- a/.agents/skills/clack-cli-patterns/SKILL.md +++ b/.agents/skills/clack-cli-patterns/SKILL.md @@ -1,6 +1,6 @@ --- name: clack-cli-patterns -description: Use when creating or modifying terminal CLI commands, prompts, or output formatting in OpenChamber. Enforces Clack UX standards with strict parity and safety across TTY/non-TTY, --quiet, and --json modes. +description: Use when creating or modifying OpenChamber CLI commands, prompts, terminal output, non-TTY behavior, `--quiet`, or `--json` behavior. license: MIT compatibility: opencode --- @@ -150,56 +150,9 @@ For each command/subcommand, manually verify: 4. non-TTY behavior (e.g. piped) 5. error path in both human and json modes -## Copy/Paste Snippets - -### Prompt Guard - -```js -if (canPrompt(options)) { - const value = await select({ - message: 'Choose an option', - options: [{ value: 'a', label: 'Option A' }], - }); - if (isCancel(value)) { - cancel('Operation cancelled.'); - return; - } -} -``` - -### Non-Interactive Fallback - -```js -if (!resolvedValue) { - if (canPrompt(options)) { - // prompt path - } else { - throw new Error('Missing required value. Provide --flag .'); - } -} -``` - -### Spinner Guard - -```js -const spin = createSpinner(options); -spin?.start('Running operation...'); -// ...work... -spin?.stop('Done'); -``` - -### JSON vs Human Output - -```js -if (options.json) { - printJson({ ok: true, data }); - return; -} - -intro('Operation'); -log.success('Completed'); -outro('done'); -``` +## Reusable Snippets + +Load `references/snippets.md` when implementing prompt guards, non-interactive fallback, spinner lifecycle, or JSON/human output branching. ## Implementation Checklist @@ -211,6 +164,6 @@ outro('done'); ## References -- Policy source: `AGENTS.md` (CLI Parity and Safety Policy) +- This skill is the canonical CLI parity and safety policy. - Terminal CLI precedent: `packages/web/bin/cli.js` - Output adapter precedent: `packages/web/bin/cli-output.js` diff --git a/.agents/skills/clack-cli-patterns/references/snippets.md b/.agents/skills/clack-cli-patterns/references/snippets.md new file mode 100644 index 0000000000..8ac30943cb --- /dev/null +++ b/.agents/skills/clack-cli-patterns/references/snippets.md @@ -0,0 +1,50 @@ +# CLI Output Snippets + +## Prompt Guard + +```js +if (canPrompt(options)) { + const value = await select({ + message: 'Choose an option', + options: [{ value: 'a', label: 'Option A' }], + }); + if (isCancel(value)) { + cancel('Operation cancelled.'); + return; + } +} +``` + +## Non-Interactive Fallback + +```js +if (!resolvedValue) { + if (canPrompt(options)) { + // prompt path + } else { + throw new Error('Missing required value. Provide --flag .'); + } +} +``` + +## Spinner Guard + +```js +const spin = createSpinner(options); +spin?.start('Running operation...'); +// ...work... +spin?.stop('Done'); +``` + +## JSON vs Human Output + +```js +if (options.json) { + printJson({ ok: true, data }); + return; +} + +intro('Operation'); +log.success('Completed'); +outro('done'); +``` diff --git a/.agents/skills/desktop-shell/SKILL.md b/.agents/skills/desktop-shell/SKILL.md new file mode 100644 index 0000000000..08717a60e0 --- /dev/null +++ b/.agents/skills/desktop-shell/SKILL.md @@ -0,0 +1,50 @@ +--- +name: desktop-shell +description: Use when changing Electron main/preload code, desktop IPC, native windows, menus, dialogs, notifications, updater behavior, deep links, SSH or tunnels, child processes, packaged startup, or Windows process spawning. +--- + +# Desktop Shell + +## Read First + +Read `packages/electron/README.md` and nearby `packages/electron` code before editing. + +## Runtime Boundary + +- Electron boots `@openchamber/web` in the same Node process and loads the UI over loopback. Do not introduce a sidecar server process. +- Keep OpenCode feature backends and shared domain logic in web/server or runtime APIs. +- Keep Electron focused on inherently native behavior: windows, menus, dialogs, notifications, updater, deep links, runtime host switching, privileged IPC, SSH, and tunnel lifecycle. +- Shared renderer-facing contracts belong in `packages/ui`; shared server behavior belongs in `packages/web`. +- Electron is the desktop release target. + +## IPC And Security + +1. Add a preload bridge shape only when renderer-facing capability changes. +2. Handle the native operation in `main.mjs`. +3. Gate privileged commands in the main process; renderer checks are not security boundaries. +4. Expose the narrowest payload and never expose filesystem, shell, tokens, or host secrets to remote pages. +5. Do not import Electron from shared UI code. + +Remote runtime pages must not gain local desktop privileges. Treat deep links, host imports, stored credentials, and runtime switching as trust-boundary operations. + +## Windows Background Processes + +Non-user-visible child processes must never flash a console window. + +- Spawn the target executable directly with `windowsHide: true`. +- Use `stdio: 'ignore'` for detached/background helpers and call `unref()` when they must outlive Electron. +- Avoid `cmd.exe /c`, batch shims, `taskkill`, `ping` delays, and pipelines that create console grandchildren. `windowsHide` reliably controls only the directly spawned process. +- Prefer native Node/Electron APIs when available. +- For delayed work that must survive app exit, spawn one first-level hidden helper, such as `powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ...`; perform delay and work inside that process with cmdlets. +- Omit hidden-process behavior only for intentionally user-visible terminals or applications. + +## Packaging And Lifecycle + +- Keep native/external modules configured according to `packages/electron/README.md` and `bundle-main.mjs`. +- Preserve startup, quit, updater, notification, and deep-link behavior across development and packaged builds. +- Ensure cleanup tolerates partial startup and repeated shutdown signals. +- Do not infer readiness from stdout when an in-process callback or returned server handle exists. + +## Validation + +Run the Electron package type-check/lint commands from `package.json` and focused tests. For startup, preload, routing, or packaging changes, test both HMR development and bundled UI mode. For Windows process work, inspect the complete process tree and verify no console flash; a successful command alone is insufficient. diff --git a/.agents/skills/drag-to-reorder/SKILL.md b/.agents/skills/drag-to-reorder/SKILL.md index b8eec042d6..76a3e165ed 100644 --- a/.agents/skills/drag-to-reorder/SKILL.md +++ b/.agents/skills/drag-to-reorder/SKILL.md @@ -1,6 +1,6 @@ --- name: drag-to-reorder -description: Use when implementing drag-to-reorder / sortable lists or chips in OpenChamber with @dnd-kit — covers the correct setup for BOTH desktop and mobile (touch), the variable-width "stretch" fix, the wrapping multi-row strategy choice, and the pitfalls (infinite update loop, offset overlay) we already hit and fixed. +description: Use when implementing or modifying OpenChamber sortable or drag-to-reorder behavior, especially `@dnd-kit`, touch/mobile interactions, variable-width items, or wrapping layouts. license: MIT compatibility: opencode --- diff --git a/.agents/skills/openchamber-change-discipline/SKILL.md b/.agents/skills/openchamber-change-discipline/SKILL.md new file mode 100644 index 0000000000..aac28f1be3 --- /dev/null +++ b/.agents/skills/openchamber-change-discipline/SKILL.md @@ -0,0 +1,122 @@ +--- +name: openchamber-change-discipline +description: Use when implementing, fixing, refactoring, or otherwise modifying OpenChamber source code, dependencies, exports, build configuration, generated assets, package contracts, or module ownership. +--- + +# OpenChamber Change Discipline + +## Core Principle + +Make the smallest complete change and validate at the narrowest level that covers the real risk. + +Identify existing behavior covered by tests or callers; preserve it unless the requested change explicitly replaces it. + +## Before Editing + +1. Read the nearest `DOCUMENTATION.md` and package `README.md` when present. +2. Inspect nearby implementation and tests before introducing a pattern. +3. Load every additional project skill whose trigger matches the change. +4. Classify the highest applicable change risk below. +5. Identify affected consumers, runtimes, persisted data, and public exports. + +When instructions materially conflict, stop and resolve the conflict instead of silently choosing one. + +## Risk Classification + +| Risk | Examples | Planning consequence | +|---|---|---| +| Local implementation | Private helper or component behavior in one package | Preserve observable behavior; validate the owning package | +| Module contract | Exported API/type or documented module invariant | Inspect consumers; update contract tests and owning docs | +| Cross-workspace contract | Shared UI/runtime/package shape consumed by multiple workspaces | Trace every actual consumer and runtime; validate across workspaces | +| Persisted or external behavior | Stored settings/data, routes, IDs, files, CLI output | Define compatibility, round-trip, failure, and conversion behavior for existing consumers | +| Platform/runtime behavior | Electron, VS Code, mobile, relay, native or packaged behavior | Run the relevant runtime/build/integration validation | + +Apply every matching category. Do not escalate local work into workspace-wide ritual, and do not treat a type-only export as local merely because it emits no JavaScript. + +## Mandatory Rules + +- Identify existing behavior covered by tests or callers; preserve it unless explicitly replaced. +- Do not add dependencies unless explicitly requested. +- Do not add compatibility paths without a concrete persisted or external consumer. +- Enforce security and correctness in core logic, not only UI controls or prompts. +- Never add, persist, or log secrets, bearer tokens, pairing data, or sensitive user content. +- Make data loss, partial failure, rollback, and fallback behavior explicit. +- Update owning documentation when module ownership, contracts, or invariants change. +- Complete the cumulative validation required by every applicable risk category. + +## Engineering Preferences + +- Prefer the smallest correct change; avoid drive-by refactors. +- Keep orchestration entrypoints thin and move domain logic to focused modules. +- Prefer explicit dependencies and dependency injection over hidden module coupling. +- Follow local TypeScript types; avoid `any`, blind casts, and guessed payload shapes. +- Prefer early returns and explicit branches over nested conditionals. + +## Review Prompts + +Before broadening a change, ask: + +- Is the new abstraction reused or merely possible to reuse? +- Is the code in the package that owns the behavior? +- Does the change alter shared UI contracts across web, desktop, VS Code, or mobile? +- Does it change persisted data, IDs, routes, exports, generated files, or package entrypoints? +- Can failure leave optimistic state, caches, files, or remote state stranded? + +For partial or destructive flows, answer explicitly: + +- What remains valid after the first failure? +- What is rolled back or cleaned up? +- What can be retried or resumed safely? +- What does the user observe? + +For persisted data, require a migration only when existing stored data needs conversion. Test downgrade compatibility only when older application versions are a concrete supported consumer. "Rollback" means preserving/restoring valid state after a failed write or migration unless a broader contract explicitly says otherwise. + +Do not hide a required architectural migration behind a local heuristic. Do not turn a local fix into a speculative rewrite. + +## Validation Matrix + +Use `package.json` scripts as the command source of truth. + +| Change | Minimum validation | +|---|---| +| Executable source | Focused tests plus package-scoped type-check and lint | +| Cross-workspace/shared contract | Workspace-wide type-check and lint plus affected builds/tests | +| Added/deleted/renamed source file, export/type/entrypoint/import shape | `bun run dead-code` in addition to relevant checks | +| Persisted or external contract | Compatibility and round-trip tests; conversion/malformed-old-data tests when old data needs migration; failed-write/migration rollback tests | +| Dependency or lockfile | Workspace-wide checks and affected builds | +| Generated asset | Regeneration check plus consumer build/test | +| Docs-only or isolated config | Narrow syntax/schema/link validation; do not run unrelated full suites | +| Platform/runtime behavior | Relevant runtime build or manual/integration check; static checks are insufficient | + +Use a sufficiently long timeout for broad checks. Report exactly what ran and what did not. + +Choose affected builds/tests by tracing real consumers and runtime boundaries, not by running everything reflexively. + +For type-only shared contracts, validate compile-time consumers. Add runtime serialization tests when the contract crosses a process, persistence, network, or untyped JavaScript boundary. + +## Test Design + +- Prefer observable contracts, state transitions, failure handling, rollback, and operation counts. +- Test private helpers through public/module behavior when that captures the risk clearly. +- Assert internal map shape, helper calls, or call order only when that structure/order is itself a contract. +- Keep refactor tests resilient to equivalent internal implementations. +- For behavior-preserving refactors, establish the current behavior before changing structure. + +## Completion Standard + +- Implement the behavior end to end, including rollback and cleanup. +- Run focused regression tests for the changed contract. +- Preserve unrelated changes encountered in shared files. +- Re-read the owning docs and update them when the implementation changed their truth. +- Do not claim runtime, relay, performance, or platform correctness from type-check/lint alone. + +## Common Failure Modes + +| Failure | Correction | +|---|---| +| Refactoring nearby code while fixing one bug | Keep the diff scoped unless the nearby change is required | +| Adding a helper used once | Keep direct code until reuse or composability is real | +| Swallowing an error for smoother UX | Preserve the failure signal and handle presentation separately | +| Updating a bridge without all runtimes | Load the runtime/API skill and make parity explicit | +| Running only broad checks | Add focused tests that exercise the changed behavior | +| Running only focused checks after a shared-contract change | Add workspace-wide validation | diff --git a/.agents/skills/performance-engineering/SKILL.md b/.agents/skills/performance-engineering/SKILL.md new file mode 100644 index 0000000000..ef992029ec --- /dev/null +++ b/.agents/skills/performance-engineering/SKILL.md @@ -0,0 +1,184 @@ +--- +name: performance-engineering +description: Use when implementing or reviewing code on interaction, render, event, polling, synchronization, list-processing, store-selector, cache, indexing, or high-volume data paths; when users report lag, freezes, jank, high CPU, memory growth, slow startup, or performance regressions; and before accepting memoization or caching as a fix for repeated work. +--- + +# Performance Engineering + +## Overview + +Optimize the amount and frequency of work before optimizing individual operations. + +**Core principle:** Make expensive work structurally unnecessary. A fast inner function still freezes the app when called millions of times on the main thread. + +## Start With A Performance Contract + +Define before editing: + +| Dimension | Required answer | +|---|---| +| Interaction | Which user action or event must remain responsive? | +| Scale | Realistic and worst-known entity counts | +| Budget | Target latency, frame time, CPU, memory, or operation count | +| Path | Main thread, worker, server, network, disk, or mixed | +| Semantics | Ordering, ownership, freshness, failure, and partial-data invariants | + +Do not optimize against a toy fixture when the report provides production scale. + +## Workflow + +### 1. Reproduce And Measure + +- Reproduce the exact interaction, not a nearby helper in isolation. +- Separate scripting, rendering, painting, network, disk, and waiting time. +- Use a profiler to identify total time and self time. +- Add operation counters when timings are noisy: selector calls, normalizations, scans, allocations, sorts, notifications. +- Capture a baseline before changing code. + +Do not infer a bottleneck from code appearance when a trace or counter can identify it. + +### 2. Write The Cost Equation + +Name every multiplying dimension: + +```text +consumers × events × projects × sessions × candidate paths +``` + +For each factor, record: + +- cardinality at production scale; +- update frequency; +- whether work happens on the main thread; +- whether multiple consumers independently derive the same result. + +Treat hidden fanout as real work. Equality checks may prevent renders while selectors, aggregation, sorting, and allocation still execute. + +### 3. Map Sources, Derived State, And Lifetimes + +Classify each input: + +- authoritative or partial; +- live or historical; +- stable or high-frequency; +- successful empty result or fetch failure; +- globally complete or complete only for one entity. + +Define invalidation before adding a cache. Prefer a stronger source of truth over inference. + +For destructive consumers, represent completeness explicitly. An incomplete empty bucket means "unknown", not "delete everything". + +Track completeness at the smallest destructive scope. One failed project/entity blocks cleanup for itself, not for unrelated complete scopes. + +### 4. Remove Work In This Order + +1. **Skip:** gate disabled paths and return on no-op updates. +2. **Narrow:** subscribe to the exact entity/field that can affect the result. +3. **Share:** compute identical derived data once for all consumers. +4. **Index:** represent the lookup direction the UI actually needs. +5. **Increment:** update only affected buckets/entities and preserve other references. +6. **Cache:** reuse pure results with explicit keys, invalidation, and memory bounds. +7. **Schedule:** defer, chunk, or move genuinely unavoidable CPU work off the interaction path. +8. **Micro-optimize:** tune regexes, loops, and allocations only after structural multipliers are gone. + +Do not jump to a worker to hide avoidable work. Do not add a global store when a local shared index has the correct lifetime. + +## Structural Pattern + +Replace repeated questions with maintained answers: + +```ts +// Bad: every consumer asks every item about every owner. +for (const project of projects) { + const items = sessions.filter((session) => belongsTo(project, session, topology)); +} + +// Good: resolve ownership once, then read direct buckets. +const sessionsByProject = new Map(); +for (const session of sessions) { + const projectId = ownership.resolve(session.directory); + if (projectId) append(sessionsByProject, projectId, session); +} +``` + +Prefer indexes keyed by stable IDs. Keep high-frequency runtime state out of metadata indexes unless it changes membership. + +## React And Store Hot Paths + +- Subscribe to leaf values, not broad collections. +- Preserve references for unaffected entities and buckets. +- Keep streaming state out of broadly consumed stores. +- Never rely on `React.memo`, `useMemo`, or Zustand equality to prevent selector execution upstream. +- Do not sort structural lists from token/delta-frequency fields. +- Coalesce repeated same-entity events and skip no-op reducer updates. +- Ensure hidden or disabled surfaces perform no ongoing work. +- Preserve scroll position synchronously with `useLayoutEffect`; do not wait visible frames before compensation. +- Distinguish viewport resize from content growth and avoid fighting browser scroll anchoring. +- Avoid textarea auto-size shrink/expand cycles when content only grows. +- Freeze structural ordering during high-frequency updates and reorder at an explicit lifecycle edge. + +## Caching Rules + +Add a cache only when all are explicit: + +- exact key and source identity; +- invalidation events; +- stale-result behavior; +- memory count and byte bounds where values can grow; +- runtime/project/user isolation where identities can collide; +- proof that caching removes enough work to meet the budget. + +A cache inside an `O(consumers × entities × candidates)` loop is a mitigation, not automatically a complete fix. + +## Verification + +Require both correctness and performance guards: + +- representative-scale fixture from the report; +- cold and warm paths when caching exists; +- median plus p95/max, not one lucky run; +- deterministic operation-count assertion when possible; +- repeated-event test for streaming/polling paths; +- no-op and unrelated-entity update tests; +- reference-stability test for unaffected buckets; +- failure, partial-data, empty-success, and stale-async-completion tests; +- memory/cache growth check for long-running paths; +- production build or equivalent runtime profile for UI interactions. + +State what was not measured. Never claim a freeze is fixed from type-check and unit tests alone. + +## Hotfix Policy + +Ship a bounded cache-only or local mitigation under deadline pressure only when: + +- it measurably meets the user-facing budget at reported scale; +- invalidation and memory behavior are correct; +- semantics are unchanged or explicitly accepted; +- remaining complexity is documented as follow-up work. + +If the interaction remains above budget, do not call the mitigation the completed performance fix. + +## Common Rationalizations + +| Rationalization | Reality | +|---|---| +| "The helper is cheap" | Multiply it by events, entities, candidates, and consumers. | +| "No component rerendered" | Selectors and equality comparisons may still burn CPU. | +| "`useMemo` fixes it" | Memoization does not help when dependencies churn or consumers duplicate work. | +| "The cache made it 10× faster" | Compare the result with the interaction budget, not only the baseline. | +| "Projects are few" | Identify the dimension that is large and the dimensions multiplying it. | +| "Move it to a worker" | Moving waste changes responsiveness, not total cost or data correctness. | +| "Empty means nothing exists" | Empty after failure or partial loading is not authoritative absence. | +| "We can optimize later" | Add a scale regression now or the multiplier will return. | + +## Exit Checklist + +- [ ] Exact interaction and production scale reproduced. +- [ ] Cost equation written and dominant multipliers removed. +- [ ] Sources of truth, completeness, and invalidation explicit. +- [ ] No broad subscription or render-time global scan on a high-frequency path. +- [ ] Unaffected references remain stable. +- [ ] Partial failure cannot trigger destructive cleanup. +- [ ] Representative benchmark meets the stated budget. +- [ ] Operation-count or repeated-event regression test prevents recurrence. +- [ ] Correctness, type, lint, and relevant runtime validations pass. diff --git a/.agents/skills/relay-transport/SKILL.md b/.agents/skills/relay-transport/SKILL.md index 9857a9eb75..46ff569eeb 100644 --- a/.agents/skills/relay-transport/SKILL.md +++ b/.agents/skills/relay-transport/SKILL.md @@ -1,6 +1,6 @@ --- name: relay-transport -description: Use when adding or changing any WebSocket, SSE, or streaming endpoint (terminal, dictation/voice, event stream, notifications), opening a WebSocket in shared UI, refactoring the runtime transport (runtime-fetch/runtime-url/runtime-switch/runtime-auth), touching anything under packages/ui/src/lib/relay or packages/web/server/lib/relay, or porting a realtime feature. These changes silently break OpenChamber's private relay (mobile→desktop over an E2EE tunnel) in ways that pass local/desktop testing and only fail over the relay on a real device. Load this before such work to know the invariants and the traps already hit. +description: Use when adding or changing OpenChamber WebSocket, SSE, streaming, realtime endpoints, shared UI sockets, runtime transport internals, private relay behavior, or files under the UI/server relay modules. license: MIT compatibility: opencode --- @@ -44,6 +44,18 @@ Adding a new WS endpoint (or porting one, e.g. the planned terminal port) requir - Relay mode routes through `runtime-switch` (activates the tunnel singleton), `runtime-fetch` (routes runtime requests through it), `runtime-url`/`runtime-socket` (tunnel-backed URLs/sockets), and `runtime-auth` (mints the URL token through the tunnel). When refactoring any of these, preserve the relay branch and the direct-URL/Electron-realtime-proxy branches — they must remain byte-identical in behavior for non-relay runtimes. - **The host dispatcher never injects credentials.** Tunneled requests carry the client's own token; the server authenticates them. Do not add host-side auth shortcuts, and do not trust loopback source address as authentication (relay traffic arrives at loopback but represents remote clients). +## Reconnect pacing + +For indefinite SSE/WebSocket reconnect loops: + +- Use exponential backoff based on consecutive failures, not a constant short delay. +- Use the long backoff cap while `navigator.onLine` is false or `document.visibilityState` is hidden. +- Treat permanent 4xx responses as long-backoff failures; keep 408 and 429 retryable. +- Make waits interruptible by `online`, visibility becoming visible, and the pipeline abort signal. +- Reset failure state only after a genuinely healthy connection. + +Blind short retries on hidden, offline, unauthorized, or stale-path clients waste battery and flood server logs. + ## Testing guidance (a stub that skips auth/origin hides the exact bugs) - Exercise the real auth and origin gates. An end-to-end test whose stub server accepts any WS upgrade will pass while the real server rejects it — this is precisely how the origin-check bug shipped. When writing a relay integration test, mirror the real gates (`ensureSessionToken` via `oc_url_token`, `isRequestOriginAllowed`) or run against the real server pieces. diff --git a/.agents/skills/settings-ui-patterns/SKILL.md b/.agents/skills/settings-ui-patterns/SKILL.md index c0d012e46e..4567c2f1a5 100644 --- a/.agents/skills/settings-ui-patterns/SKILL.md +++ b/.agents/skills/settings-ui-patterns/SKILL.md @@ -1,291 +1,68 @@ --- name: settings-ui-patterns -description: Use when creating or modifying UI components, styling, or visual elements related to Settings in OpenChamber. -license: MIT -compatibility: opencode +description: Use when creating or modifying OpenChamber Settings pages, dialogs, controls, configuration surfaces, responsive Settings layouts, or Settings search behavior. --- -# Settings UI Patterns Skill +# Settings UI Patterns -## Purpose -This skill provides instructions for creating or redesigning Settings pages, informational panels, and configuration interfaces within the OpenChamber application. +## Required Companion Skills -## Current Canonical Look (2026) -Use this as source of truth for new settings UI work. +- Load `theme-system` for colors, buttons, icons, and visual states. +- Load `locale-ui-patterns` for every visible string, tooltip, placeholder, and accessible label. +- Load `ui-api-decoupling` when a setting reads/writes runtime data or adds a capability. -- **Flat hierarchy first**: Prefer spacing + typography hierarchy over boxed backgrounds. -- **No unnecessary wrappers**: Avoid extra section wrappers that mix unrelated controls. -- **No redundant section titles**: Do not add headers like `Theme Preferences` or `Scaling & Layout` when controls are already self-explanatory. -- **Compact controls**: Option chips and radio rows should be dense, not tall. -- **Left-leading state icon**: Radio/checkbox state icon appears before text. -- **Subtle state contrast**: Inactive radio labels should be visibly dimmer than active labels. -- **Minimal row chrome**: Avoid row hover/background highlighting by default; keep only where explicitly needed. +When examples conflict, shared component/theme and localization contracts win. Stop on unresolved material conflicts. -## Typography Guidelines -Always utilize the standard OpenChamber typography classes defined in `packages/ui/src/lib/typography.ts`. +## Canonical Direction -- **Page Title**: Use `typography-ui-header font-semibold text-foreground` for the top-most title of a settings page/dialog. -- **Section Header**: Use `typography-ui-header font-medium text-foreground` for settings sections (e.g. `Notification Events`, `Session Defaults`). -- **Control Group Header**: Use `typography-ui-header font-medium text-foreground` (or `font-normal` if it reads too loud) for grouped controls inside a section (e.g. `Default Tool Output`, `Diff Layout`). -- **Values / Primary Text**: Use `typography-ui-label text-foreground`. Add `tabular-nums` if displaying numbers or stats to ensure vertical alignment. -- **Option Labels**: Use non-bold label text in compact option controls (`font-normal` when needed to override). -- **Meta / Helper Text**: Use `typography-meta text-muted-foreground` or `typography-small text-muted-foreground` for supplemental text. +- Prefer flat hierarchy built with spacing and typography. +- Avoid unnecessary cards, wrappers, row chrome, and redundant headings. +- Keep controls compact and align related rows consistently. +- Put checkbox/radio state before labels. +- Use subtle, stable selected-state styling without layout shifts. +- Preserve responsive wrapping/stacking and long-text behavior. -## Layout and Spacing Patterns +## Load References By Task -### 1. Main Backgrounds -Main wrappers should generally use `bg-background` or `bg-[var(--surface-background)]`. Ensure adequate padding (e.g., `px-5 py-6` or `p-6`). +| Task | Required reference | +|---|---| +| Page hierarchy, typography, spacing, columns, responsive grids | `references/layout.md` | +| Chips, radios, checkboxes, numeric overrides, inputs, icon actions, pickers | `references/controls.md` | +| Adding/moving controls, pages, availability, anchors, or search entries | `references/search.md` | -### 2. Subsection Grouping -Group related controls with vertical spacing, not mandatory cards. +Load every matching reference before editing. -- Use `space-y-3` between logical subsections. -- Use `p-2` for subsection internal padding. -- Avoid adding `bg-[var(--surface-elevated)]` unless there is a clear reason. -- Avoid extra row decorations (`rounded-md`, hover fills) unless there is explicit UX value. +## Quick Control Selection -### 3. Header-to-Content Hierarchy (critical) -When removing cards/background wrappers, spacing must be rebalanced so header ownership stays clear. +| Need | Shared pattern | +|---|---| +| Short selectable options | `Button variant="chip" size="xs"` + `aria-pressed` | +| Mutually exclusive mode list | `Radio` rows | +| Boolean | `Checkbox` | +| Numeric value/override | `NumberInput` | +| Text/path | `Input` with shared adjacent actions | +| Icon-only action | `Button size="icon"` + sprite `Icon` + localized `aria-label` | -- Keep **section-to-section spacing larger** than **header-to-own-content spacing**. -- Typical pattern: - - header wrapper `mb-1 px-1` - - content wrapper `pt-0 pb-2 px-2` - - outer section spacing `mb-8` -- Do not leave legacy `mb-3` style gaps after flattening a section; it makes headers look detached. +Do not introduce `ButtonSmall`, direct Remixicon components, hardcoded user-facing strings, or one-off color/button systems. -### 4. Headerless Blocks (when context is obvious) -If the page title already provides enough context, remove redundant local headers and place controls directly below the title. +## Settings Search Contract -- Example: project page identity controls can sit directly under project name/path. -- Tighten top gap for this pattern (e.g. top header `mb-4` instead of larger section spacing). +Every stable Settings control addition or move must consider search in the same change: -```tsx -
-
...
-
...
-
-``` +- explicit registry item in `packages/ui/src/lib/settings/search.ts` when searchable; +- matching `data-settings-item` anchor; +- localized title/description keys; +- availability matching actual render conditions; +- state preparation before highlighting conditional targets. -## Structural Patterns +Dynamic entity rows normally are not indexed. Load `references/search.md` for exact rules. -### 1. Segmented Option Buttons (compact) -Use for short option sets where button-style segmented choice reads best (e.g. Default Tool Output). +## Review Checklist -```tsx -
- - Collapsed - -
-``` - -### 2. Radio Option Lists (compact rows) -Use for mutually exclusive mode/layout settings (e.g. Diff Layout, Diff View Mode). - -- Use shared `Radio` component from `@/components/ui/radio`. -- Icon first, label second. -- Row container compact: `py-0.5`. -- Inactive label can use `text-foreground/50`. - -```tsx -
-
- - Dynamic -
-
-``` - -### 3. Checkbox Setting Rows -Use shared `Checkbox` component from `@/components/ui/checkbox` for boolean toggles. - -- Icon first, text immediately after (`gap-2`). -- Typical row spacing for checkbox rows: `py-1.5`. -- Keep row click and keyboard toggle support. -- Prefer checkbox over binary show/hide button pairs for pure boolean state. - -```tsx -
- - Show Dotfiles -
-``` - -### 4. Invisible Two-Column Alignment -Use consistent label/control columns across settings rows so controls align on a shared vertical line. - -- Desktop row pattern: `flex items-center gap-8` -- Label column width: `w-56 shrink-0` -- Control cluster: `w-fit` - -```tsx -
- Interface Font Size -
...
-
-``` - -#### Disabled control rule -If a control is unavailable, disable the control only. Do not dim the label row by default. - -#### Width-matching rule -When matching visual widths across different rows, compare full row footprint (control + adjacent action buttons), not just input width. - -### 5. Theme Row Composition -For theme controls in Appearance: - -- `Color Mode` header on first line; option chips below it. -- `Light Theme` and `Dark Theme` on one row where possible, wrapping on small widths. -- Keep selectors near labels and aligned to existing column rhythm. -- Replace persistent helper text with an info tooltip icon near the related action. - -```tsx -
-
Light Theme ...
-
Dark Theme ...
-
-``` - -### 6. Numeric Controls in Settings -Use compact stepper input (`- value +`) plus reset button. - -- Prefer shared `NumberInput` stepper style over slider + numeric combo in dense settings pages. -- Keep reset button adjacent to control (`gap-2`). -- Avoid using Tailwind `overflow-hidden` on mobile for controls; `packages/ui/src/styles/mobile.css` forces `.overflow-hidden { overflow-y: auto !important; }`. - Use `overflow-x-hidden overflow-y-hidden` if you truly need clipping. -- Touch devices: `packages/ui/src/styles/mobile.css` enforces `min-height: 36px` on `button`. If you build custom segmented controls with ` +``` + +## Radio Row + +```tsx +
+
+ + + {t(labelKey)} + +
+
+``` + +## Checkbox Row + +```tsx +
+ + {t(labelKey)} +
+``` + +Preserve row click and keyboard behavior when the container is interactive. + +## Optional Numeric Override + +Empty means “inherit/default.” Provide fallback stepping and explicit clear: + +```tsx + setTemperature(undefined)} + min={0} + max={2} + step={0.1} + inputMode="decimal" + emptyLabel="—" +/> +``` + +Keep reset adjacent. Prefer an info tooltip over persistent helper text when the explanation is secondary. + +## Inputs And Icon Actions + +```tsx +
+ + +
+``` + +- Prefer compact inputs in dense rows. +- Avoid large select triggers in Settings. +- Use shared `Button` and sprite `Icon`, never wrapper buttons or direct Remixicon imports. + +## Mobile Constraints + +- `packages/ui/src/styles/mobile.css` may force `.overflow-hidden` to scroll; use explicit x/y clipping only when required. +- Touch CSS enforces minimum button height. Do not put custom segmented buttons in a container too short for them. + +## Picker Rows + +- Place icon/color palettes beneath their label. +- Keep option dimensions and gaps consistent. +- Use stable border/ring/background selection; avoid scale transforms that shift layout. diff --git a/.agents/skills/settings-ui-patterns/references/layout.md b/.agents/skills/settings-ui-patterns/references/layout.md new file mode 100644 index 0000000000..e65310ae6c --- /dev/null +++ b/.agents/skills/settings-ui-patterns/references/layout.md @@ -0,0 +1,53 @@ +# Settings Layout + +## Visual Hierarchy + +- Prefer spacing and typography over boxed backgrounds. +- Avoid wrappers that mix unrelated controls. +- Omit redundant headings when page context already names the controls. +- Keep controls compact and row chrome minimal. +- Place checkbox/radio state before its label. +- Dim inactive option labels subtly; do not use transform jumps. + +## Typography + +Use classes from `packages/ui/src/lib/typography.ts`: + +- Page title: `typography-ui-header font-semibold text-foreground` +- Section header: `typography-ui-header font-medium text-foreground` +- Control group: `typography-ui-header font-medium` or `font-normal` when needed +- Values/labels: `typography-ui-label text-foreground` +- Helper/meta: `typography-meta text-muted-foreground` or `typography-small text-muted-foreground` +- Numeric values: add `tabular-nums` + +## Spacing + +- Keep section-to-section spacing larger than header-to-content spacing. +- Typical flat section: header `mb-1 px-1`, content `pt-0 pb-2 px-2`, outer `mb-8`. +- Group related controls with `space-y-3` and modest internal padding such as `p-2`. +- Avoid elevated backgrounds, rounded rows, and hover fills without explicit UX value. + +## Alignment + +For consistent desktop columns: + +```tsx +
+ {t(labelKey)} +
...
+
+``` + +- Let narrow layouts stack or wrap. +- Compare the complete control footprint, including adjacent actions, when matching widths. +- Disable only the unavailable control; do not dim the entire label row by default. + +## Responsive Grids + +Use a one-column base and introduce columns at a deliberate breakpoint: + +```tsx +
+``` + +Template fields commonly use `grid grid-cols-1 gap-2 md:grid-cols-2 md:gap-3` with flat `p-2` cells. diff --git a/.agents/skills/settings-ui-patterns/references/search.md b/.agents/skills/settings-ui-patterns/references/search.md new file mode 100644 index 0000000000..a3d573c109 --- /dev/null +++ b/.agents/skills/settings-ui-patterns/references/search.md @@ -0,0 +1,42 @@ +# Settings Search + +Settings search uses an explicit registry; it does not scrape JSX. + +## Required Integration + +- Add/update items in `packages/ui/src/lib/settings/search.ts`. +- Add a matching `data-settings-item="..."` anchor to the rendered setting. +- Use localized labels/descriptions from every `packages/ui/src/lib/i18n/messages/*.settings.ts` dictionary. +- For a new top-level page, add metadata in `packages/ui/src/lib/settings/metadata.ts` and searchable content unless the page is purely navigational. + +## Registry Rules + +- Index stable controls, section headers, and static create/connect actions. +- Use IDs matching page and target, such as `appearance.language`. +- Prefer the visible label key as `titleKey`. +- Add `descriptionKey` only when it improves context. +- Add useful synonyms/acronyms as keywords. +- Do not generate items for dynamic entities such as individual agents, providers, projects, skills, hosts, or sessions. + +## Conditional Targets + +- Do not index a target hidden behind selected-entity state unless search selection prepares that state first. +- Keep item `isAvailable` identical to actual render visibility. +- Put page-level availability in `metadata.ts` and item-specific guards in `search.ts`. +- Distinguish desktop shell from local desktop origin when the feature requires local privileges. +- For split pages, index predictable static surfaces and update `prepareSettingsSearchTarget` when a result must open a draft/editor before highlighting. + +## Highlight Anchor + +- Put `data-settings-item` on the smallest stable container that visually owns the setting. +- Do not add layout-only wrappers solely for search. +- Keep highlight styling token-based and subtle; it lives under `[data-settings-search-highlight="true"]` in `packages/ui/src/index.css`. + +## Audit + +- Every registry ID has a matching anchor. +- Every title/description key exists in every Settings locale. +- Every non-navigational page has appropriate coverage. +- Search visibility matches platform/runtime/mobile rendering. +- Conditional state is prepared before highlight. +- Empty-query Settings navigation remains unchanged. diff --git a/.agents/skills/sync-state-invariants/SKILL.md b/.agents/skills/sync-state-invariants/SKILL.md new file mode 100644 index 0000000000..58f574fb33 --- /dev/null +++ b/.agents/skills/sync-state-invariants/SKILL.md @@ -0,0 +1,109 @@ +--- +name: sync-state-invariants +description: Use when changing session synchronization, bootstrap or reconnect state, event reducers, polling, optimistic updates, message queues, live activity, ordering/reconciliation, runtime-scoped caches, or directory-dependent session behavior. +--- + +# Sync State Invariants + +## Read First + +Read `packages/ui/src/sync/DOCUMENTATION.md` and the nearest owning module documentation before editing. + +## Sources Of Truth + +Classify every input before deriving state: + +| Input | Valid use | +|---|---| +| Directory child store | Live per-directory session/message/status/permission state | +| Global sessions store | Complete global active/archived cache and retention/sidebar coverage | +| Persisted history/cache | Startup continuity and context restoration, never proof of current activity | +| Optimistic shadow state | Temporary UI continuity until authoritative reconciliation | + +Prefer deterministic authoritative records over heuristics. Derive live behavior from live channels, not historical anomalies. + +## Failure Is Not Empty + +Any authoritative loader whose result can replace, delete, or clear state must distinguish failure from successful empty data. + +Use an existing pattern: + +- Throw when an outer logical block can catch and preserve prior state. +- Return `T | null` when follow-up work must continue and `null` exclusively means fetch failure. + +Never swallow an SDK/API error into `[]`, `{}`, or another valid empty success. Verify that callers skip destructive replacement after failure. + +Track completeness at the smallest entity/scope. One failed project or directory blocks destructive work for itself, not for unrelated complete scopes. + +## Live And Historical State + +- Use historical state to restore context, not to infer ongoing execution. +- Scope delayed-live fallbacks to the active entity and clear them when authoritative state arrives. +- Do not let stale persisted data keep a fallback active indefinitely. +- Define field precedence when global and local/live snapshots feed the same view. +- Use one ordering/rank source for all views of the same entities. + +## Event Reducers + +- Clone only fields the event mutates; preserve every unrelated reference. +- Return no change for semantically identical events. +- Gate scans behind cheap event/entity checks. +- Coalesce repeated same-entity events without violating ordering. +- Reject stale async/event completions using generation or authoritative timestamps. +- Do not widen a narrow fallback to arbitrary historical records. + +For streaming-frequency work, also load `performance-engineering`. + +## Polling And Bootstrap + +- Preserve rich fields when lightweight polling omits them. +- Use cheap change detection before heavy per-directory fetches. +- Treat startup 502/503 as transient with bounded retry/recovery. +- A retry loop requires a real failure signal; swallowed errors disable retries. +- Preserve previous authoritative state during transient bootstrap/reconnect failures. + +## Optimistic Updates + +- Insert optimistic data into the visible store and a separate shadow tracker. +- Use client-generated IDs accepted and echoed by the server to reconcile in place. +- Remove optimistic data from both visible and shadow state on failure. +- Reconcile deterministically on authoritative fetch/event; do not guess from unrelated events. +- Stabilize callbacks stored in module-level refs to avoid effect loops. + +## Session And Queue Consistency + +- Capture provider, model, agent, variant, and other send configuration when queueing. +- Do not re-resolve queued configuration from mutable current state at send time. +- Preserve server-backed attachments and convert paths at the transport boundary. +- Pass a directory hint when a newly created session is not indexed yet. +- Read mutable current directory at call time; never cache it in a long-lived closure. + +## Cache And Lifecycle + +- Match session-store limits to loaded data before events can trigger trimming. +- Invalidate message/prefetch/file caches on mutation and session eviction. +- Key runtime-scoped caches by runtime identity when IDs or paths can collide. +- Clean optimistic and local cache state after partial failures. + +## Verification + +Cover the relevant lifecycle, not only static state: + +- fresh bootstrap and successful empty result; +- fetch failure preserving prior state; +- reconnect/retry and stale completion; +- repeated/no-op/out-of-order events; +- optimistic success, reconciliation, and rollback; +- create, stream, abort, permission, archive/delete, and revisit when session behavior changes; +- partial multi-directory/project failure; +- runtime or worktree switch with dynamic directory resolution. + +## Red Flags + +- Fetch helper catches and returns `[]`. +- Historical message/session data drives a live spinner. +- One failed entity blocks or clears all entities. +- Light polling overwrites fields it did not fetch. +- Queue reads current model/agent at send time. +- New session lookup assumes SSE already indexed it. +- Optimistic data has no shadow entry or rollback. diff --git a/.agents/skills/theme-system/SKILL.md b/.agents/skills/theme-system/SKILL.md index 422c69beb7..71c9dafd65 100644 --- a/.agents/skills/theme-system/SKILL.md +++ b/.agents/skills/theme-system/SKILL.md @@ -1,350 +1,80 @@ --- name: theme-system -description: Use when creating or modifying UI components, styling, visual elements, or icons in OpenChamber. All UI colors must use theme tokens - never hardcoded values or Tailwind color classes. All icons must use the shared Icon component from the SVG sprite system - never import from @remixicon/react directly. -license: MIT -compatibility: opencode +description: Use when creating or modifying OpenChamber UI components, styling, colors, buttons, visual states, themes, or icons. --- -## Overview +# Theme System -OpenChamber uses a JSON-based theme system. Themes are defined in `packages/ui/src/lib/theme/themes/`. Users can also add custom themes via `~/.config/openchamber/themes/`. +## Core Rules -**Core principle:** UI colors must use theme tokens - never hardcoded hex colors or Tailwind color classes. +- Use semantic OpenChamber theme tokens; never hardcode hex colors or generic Tailwind palette colors. +- Use shared UI primitives before introducing feature-local controls. +- Use the shared `Button`; do not create button wrappers such as `ButtonSmall` or `ButtonLarge`. +- Use the sprite-based `Icon`; never import icons directly from `@remixicon/react`. +- Apply hover tokens only to interactive elements. +- Use status colors only for actual status/feedback. +- Use selection tokens for selected state and primary tokens for primary actions. -## When to Use +## Load References By Task -- Creating or modifying UI components -- Working with colors, backgrounds, borders, or text -- **Working with icons — adding, changing, or creating icon usages** +| Task | Required reference | +|---|---| +| Choosing colors/tokens or reviewing styled examples | `references/tokens-and-examples.md` | +| Adding, converting, storing, or generating icons | `references/icons.md` | +| Adding built-in or custom themes | `references/adding-themes.md` | -## Quick Decision Tree +Load every matching reference before editing. Settings work must also load `settings-ui-patterns`; user-facing or accessible text must load `locale-ui-patterns`. -1. **Code display?** → `syntax.*` -2. **Feedback/status?** → `status.*` -3. **Primary CTA?** → `primary.*` -4. **Interactive/clickable?** → `interactive.*` -5. **Background layer?** → `surface.*` -6. **Text?** → `surface.foreground` or `surface.mutedForeground` +## Token Decision -## Critical Rules +1. Code display -> `syntax.*` +2. Error/warning/success/info -> `status.*` +3. Primary CTA -> `primary.*` +4. Hover/pressed/focus -> `interactive.*` +5. Selected/active state -> `interactive.selection*` +6. Background/text/border layer -> `surface.*` and semantic utility classes -- `surface.elevated` = inputs, cards, panels -- `interactive.hover` = **ONLY on clickable elements** -- `interactive.selection` = active/selected states (not primary!) -- Status colors = **ONLY for actual feedback** (errors, warnings, success) -- Input footers = `bg-transparent` on elevated background +Prefer CSS variables/classes for component styling. Use `useThemeSystem()` only when an API requires resolved color values. -## Button Rules (MANDATORY) +## Button Contract -Use only the shared `Button` component from `packages/ui/src/components/ui/button.tsx`. +Use `Button` from `packages/ui/src/components/ui/button.tsx`. -- Do not create wrapper button components (for example `ButtonLarge`, `ButtonSmall`). -- Do not hardcode button height/padding classes when a `size` variant exists. -- Use semantic button variants consistently; avoid ad-hoc one-off button styling. +| Variant | Use | +|---|---| +| `default` | Primary local action | +| `outline` | Visible secondary action | +| `secondary` | Soft secondary action | +| `ghost` | Quiet row/toolbar action | +| `destructive` | Destructive action | +| `chip` | Compact selectable option with `aria-pressed` | +| `link` | Rare inline text action | -### Allowed Button Variants +| Size | Use | +|---|---| +| `xs` | Dense row/list control | +| `sm` | Compact action | +| `default` | Standard action | +| `lg` | Prominent action | +| `icon` | Icon-only square action | -| Variant | Use for | Token direction | -|-------|-------|-------| -| `default` | Primary action in a local section/dialog | `primary.*` | -| `outline` | Secondary visible action | `surface.elevated` + `interactive.*` | -| `secondary` | Soft secondary action | `interactive.hover` / `interactive.active` | -| `ghost` | Low-emphasis row/toolbar action | transparent + `interactive.hover` | -| `destructive` | Destructive actions (`Delete`, `Revert all`) | `status.error*` | -| `link` | Rare inline text action only | text-link style | +Do not hardcode button height/padding when a size variant exists. Do not recreate selection/destructive styling with ad-hoc classes. -### Allowed Button Sizes +## Icon Contract -| Size | Use for | -|------|---------| -| `xs` | Dense controls in rows/lists | -| `sm` | Default compact action buttons | -| `default` | Standard form/page actions | -| `lg` | Prominent large actions | -| `icon` | Icon-only square button | - -### Button Selection Quick Guide - -1. Main CTA in section/dialog -> `default` -2. Side action next to CTA -> `outline` -3. Quiet auxiliary action -> `ghost` -4. Dangerous action -> `destructive` -5. Tiny row action -> keep same variant, set `size="xs"` - -### Never Use - -- Hardcoded hex colors (`#FF0000`) -- Tailwind colors (`bg-white`, `text-blue-500`, `bg-gray-*`) -- Deprecated: `bg-secondary`, `bg-muted` - -## Usage - -### Via Hook ```tsx -import { useThemeSystem } from '@/contexts/useThemeSystem'; -const { currentTheme } = useThemeSystem(); +import { Icon } from '@/components/icon/Icon'; -
+ ``` -### Via CSS Variables -```tsx -
-``` - -## Color Tokens - -### Surface Colors - -| Token | Usage | -|-------|-------| -| `surface.background` | Main app background | -| `surface.elevated` | Inputs, cards, panels, popovers | -| `surface.muted` | Secondary backgrounds, sidebars | -| `surface.foreground` | Primary text | -| `surface.mutedForeground` | Secondary text, hints | -| `surface.subtle` | Subtle dividers | - -### Interactive Colors - -| Token | Usage | -|-------|-------| -| `interactive.border` | Default borders | -| `interactive.hover` | Hover on **clickable elements only** | -| `interactive.selection` | Active/selected items | -| `interactive.selectionForeground` | Text on selection | -| `interactive.focusRing` | Focus indicators | - -### Status Colors - -| Token | Usage | -|-------|-------| -| `status.error` | Errors, validation failures | -| `status.warning` | Warnings, cautions | -| `status.success` | Success messages | -| `status.info` | Informational messages | - -Each has variants: `*`, `*Foreground`, `*Background`, `*Border`. - -### Primary Colors - -| Token | Usage | -|-------|-------| -| `primary.base` | Primary CTA buttons | -| `primary.hover` | Hover on primary elements | -| `primary.foreground` | Text on primary background | - -**Primary vs Selection:** Primary = "click me" (CTA), Selection = "currently active" (state). - -### Syntax Colors - -For code display only. Never use for UI elements. - -| Token | Usage | -|-------|-------| -| `syntax.base.background` | Code block background | -| `syntax.base.foreground` | Default code text | -| `syntax.base.keyword` | Keywords | -| `syntax.base.string` | Strings | -| `syntax.highlights.diffAdded` | Added lines | -| `syntax.highlights.diffRemoved` | Removed lines | - -## Examples - -### Input Area - -```tsx -const { currentTheme } = useThemeSystem(); - -
-