Root AGENTS.md becomes a map plus cross-cutting rules; folder rules live in one AGENTS.md per package and top-level folder, checked by check:docs. CONTRIBUTING.md owns process; README and the docs page link to it. Release preparation copies the root LICENSE into every published package, Core, CLI, and MCP gain READMEs, and the release workflow test packs its fixture in-process instead of through npm.
11 KiB
App
Root Tauri/Vite app. Services and state live under src/app/**, views under src/views/**, UI under src/components/** (UI section below). Use @/ for cross-directory imports. Detect desktop with IS_TAURI.
Editor session
src/app/editor/session/create.tswraps Core: it creates reactive state, callscreateEditor(), and assembles document I/O, autosave, export, vector edit, pen resume, flashes, profiler, and mobile clipboard. Tabs live insrc/app/tabs/; active editor access insrc/app/editor/active-store/.- Use editor actions (
clearSelection(),select(),setTool()), never direct state assignments (packages/core/AGENTS.md, Editor). - File System Access APIs are browser APIs, not Tauri-only. Keep the Safari download fallback and defer
revokeObjectURL. - Vectorize provider clients, preferences, and lazy credential resolution live under
src/app/editor/vectorize/; conversion itself is in Core.
Settings, credentials, storage
- Reactive settings workflows live under the owning domain's
settings/folder (for examplesrc/app/ai/models/settings/profile-editor/{use,selection,connection}.ts), not a global composables bucket.use.tsorchestrates; focused siblings hold substantial sub-workflows. Persistence and external operations stay in domain services; pure option projections are ordinary functions. Return operation outcomes rather than importing dialogs, routers, or toasts into workflows. Guard async results against changed targets. - Persistence outcomes (
saved,failed,partial) stay in the domain: preferences and a native credential store cannot transact together. Preserve retryable drafts, reuse already-persisted identities on retries, and surface the partial-save warning. Never render raw credential backend errors or secrets. - New explicit settings forms use headless VeeValidate v5 with native Valibot schemas and existing controlled UI components; form state lives in the owning settings domain. VeeValidate owns validation and submission state; domain workflows keep their own pending/lifecycle guards. Saved secrets and replacement-secret drafts stay outside form snapshots and devtools. A credential-store failure does not make the entered key invalid.
- Credential persistence lives under
src/app/settings/credentials/. Settings components receiveCredentialManagerand may inspect status, replace, or clear; runtime adapters receiveCredentialResolverand resolve secrets at operation time. Components must not read saved secrets or keep them in long-lived reactive refs; keep newly entered secrets short-lived. Non-secret provider preferences stay in normal settings storage. - Tauri stores secrets in the system credential store through
desktop/src/credentials.rs; browsers default to WebCrypto-encrypted IndexedDB and may explicitly opt out to session-only memory. Native failures must never silently fall back to browser or plaintext storage. New integration credentials use stableCredentialRefvalues and join the unified Settings surface rather than feature-local key forms. - Storage-provider schemas and runtime adapters live under
src/app/integrations/storage/; non-secret preferences and credential references stay separate. Local-first document caching and outbox synchronization live undersrc/app/storage/. A remote storage binding augments document source state and must not replace local file identity.
AI, ACP, automation, collaboration
src/app/ai/tools/index.tsbinds Core ToolDefs to the active editor'sFigmaAPI. The chat and ACP prompt compose the Core authoring reference rather than copying it (packages/core/AGENTS.md, Tools).- ACP transport lives under
src/app/ai/acp/**; provider definitions inpackages/core/src/constants.ts; profiles insrc/app/ai/models/**. Keep provider connections, reusable profiles, and role assignments separate, and resolve credentials lazily. ACP process changes require checkingdesktop/capabilities/**. - Browser-native WebMCP registration lives under
src/app/automation/webmcp/, consumes per-tool exposure metadata, and is feature-detected throughdocument.modelContext. App completion undersrc/app/automation/execution/loads fonts after commit. - Collaboration lives under
src/app/collab/**on Trystero, Yjs, and awareness; preserve crypto-safe room IDs and peer cleanup.
Browser baseline
The supported browser baseline lives in src/app/shell/support/baseline.ts and feeds the Vite build.target, the startup gate, and the documented system requirements; change all three together, including desktop/tauri.conf.json minimumSystemVersion. Vite lowers syntax but never polyfills APIs, so two data-driven checks enforce the baseline: the app and browser-shipped packages pin TypeScript lib to ES2023 (the last edition those engines implement fully), so newer built-ins fail type-checking, and compat/compat (eslint-plugin-compat under oxlint, fed the same browsers from settings.browsers) rejects Web APIs they lack. Do not raise the lib to ESNext in those tsconfigs; use createDeferred() from src/app/runtime/deferred.ts instead of Promise.withResolvers(). Node-only packages (cli, mcp, harness) are exempt from both. tests/app/shell/support/baseline.test.ts keeps the tsconfigs and oxlint browsers in step with the baseline. src/main.ts must stay a tiny gate that only dynamically imports src/boot.ts, so an unsupported engine can still render src/app/shell/support/ guidance.
Shell
- Browser and native menus share
src/app/shell/menu/schema.ts; handle IDs inuse.tsor editor commands, and regeneratedesktop/generated/menu.jsonwithbun run generate:tauri-menu. - Motion policy lives in
src/app/shell/motion/: resolve the persisted System/Off preference and OS reduction once. The rootdata-motionattribute and the Tailwindmotion-safe/motion-reducevariants represent the effective policy, including portalled content. Use the policy-aware Motion adapters rather than repeating preference conditionals in components. - Keep the app manifest in
vite/pwa.ts; useBrandMarkfor in-app branding; never symlink web assets to desktop icons (tools/AGENTS.md, Brand assets).
UI
src/components/ui/** is generic, store-free design-system code grouped by family: {button,input,select,toggle,dialog,panel,binding,feedback,overlay,menu,paint}/. Do not create a folder named after a single component. Feature controls stay in their domain. Theme families mirror these under src/theme/; feature themes stay separate. Use explicit imports without old-path forwarding shims.
Building blocks
- Use Reka UI primitives and typed Tailwind Variants themes under
src/theme/**; merge per-instanceuislot overrides, exposeclassfor single-root components, and do not add one-off class props. UseUIcasing in type names. App wrappers around SDK primitives use shared UI helpers rather than scattered raw classes. - Tailwind 4 and
tw-animate-css; no static inline styling or component<style>blocks. Dynamic:stylebindings are allowed for runtime geometry and CSS variables. - Bind visual state through semantic
data-*attributes. Steiger rejects template-timeuse*UI(), visual-state utility branches, and raw SVG app icons. Tip, not nativetitle; Lucide/Iconify components, not raw SVG or Unicode icons;e.code, note.key, for modified shortcuts.- App dialogs compose the Reka-backed components under
src/components/ui/dialog/and the typed theme insrc/theme/dialog.ts. Do not repeat portal, overlay, content, header, or footer infrastructure in feature dialogs. - SDK property primitives, binding fields, commands, and i18n:
packages/vue/AGENTS.md.
Settings
- Compose Settings sections with
SettingsSectionand itstitle,description,actions, and default content slots. It owns heading association and internal spacing;SettingsGroupowns bordered row grouping. Do not repeat section, header, or spacing markup per feature. - Settings components own layout, translated copy, confirmation visibility, and emits; small presentation-only computed bindings may stay in components. Workflows, persistence, and credentials:
src/AGENTS.md. SettingsSaveFeedbackmaps domain outcomes (saved,failed,partial) toAppAlert.SettingsLinkowns external-link styling, the icon, and native opening for provider key pages and setup guides; keep arrow glyphs out of translated labels.
Feedback and forms
AppAlert(src/components/ui/feedback/AppAlert.vue, themesrc/theme/feedback/alert.ts) for persistent contextual errors, warnings, recovery guidance, and informative results, with translatedheading/descriptionand anactionsslot. Do not hand-roll alert markup or colored error paragraphs. Alerts announce changes without taking keyboard focus.- The toast service is for transient confirmations such as copying or completing an action after its view closes. Never show a toast and an alert for the same event; partial saves and actionable failures must not disappear in a toast.
- Field validation stays inline in the shared field component with
aria-invalid, error text before hints, and first-invalid-field focus. - Ordinary labels such as Running/Stopped remain status text or badges. Destructive confirmation belongs in the shared confirmation dialog.
Animations
- Tailwind transitions and
tw-animate-cssfor simple state changes and enter/exit; the existingmotion-vdependency for gesture-driven motion, coordinated layout changes, and springs. Do not add another animation library. - Store-free presets live in
src/theme/motion/and compose into owning themes; keep what moves, geometry, and feature-specific spring values local. Share repeated duration and easing values. Policy resolution: Shell above. - Collapsibles use Reka state attributes and measured CSS variables with
animate-collapsible-down/animate-collapsible-up; keep padding and borders inside the animated height wrapper. - Respect
prefers-reduced-motionin CSS and Motion; simplify nonessential motion while preserving state changes and feedback. Verify opening, closing, interrupted transitions, reduced motion, and scroll behavior. Expanding historical chat content must not force the transcript to the bottom.
Storybook
- Colocate
ComponentName.stories.tswithComponentName.vue; multipart compositions may use a descriptive family name. Preserve explicit titles and exported story names during moves. Default playgrounds stay static; interaction flows get named stories. Prefer inline story fixtures for small app-local states; use colocatedexamples/<Variant>.vueSFCs for substantial templates or fixtures that need SFC template/slot typing, including app-only fixtures (shared SDK examples followpackages/vue/AGENTS.md, Documentation). Story templates compile at runtime, so they must be plain JavaScript with no TypeScript syntax, andicon-lucide-*tags do not resolve there; import icons from~icons/...and register them. - Isolated visual states of feedback components belong in stories, not Playwright application screenshots. Do not add automated tests or snapshot baselines for CSS-only changes (spacing, sizing, colors, breakpoints); verify those visually. Settings E2E covers integration behavior: feedback appearance, validation and focus, retained drafts, successful retries.