···11+# Agent skills (OpenCode)
22+33+Skills live in subfolders with `SKILL.md` (see [OpenCode skills docs](https://opencode.ai/docs/skills)).
44+55+**Cursor:** after clone, link the design skill so Cursor discovers it:
66+77+```bash
88+mkdir -p .cursor/skills && ln -sf ../../.opencode/skills/textile-design .cursor/skills/textile-design
99+```
1010+1111+Run from the repository root (`textile/`).
+77
.opencode/skills/textile-design/SKILL.md
···11+---
22+name: textile-design
33+description: Apply Textile's design system when building or modifying UI in the Textile Electron app. Covers calm-but-alive aesthetic, semantic theming via CSS variables, radius/spacing/motion tokens, accessibility for Electron + macOS, and canonical component patterns. Use when adding, editing, or reviewing any React component, page, modal, or styling in textile/.
44+---
55+66+# Textile design
77+88+## Philosophy
99+1010+Textile is a **calm, lived-in surface**: low noise, generous rhythm, and motion that reassures without demanding attention. The product tenets in `PRODUCT.md` apply directly—**frictionless access**, **editor-grade performance**, and **the user should live inside Textile**. UI work should feel like a premium home base, not a flashy landing page.
1111+1212+This skill intentionally **differs** from maximalist “frontend-design” prompts: avoid novelty for its own sake, loud palettes, and busy animation. **Refinement and consistency** carry the premium feel.
1313+1414+## Before you ship UI
1515+1616+Use this checklist on every new or changed surface:
1717+1818+- [ ] **Colors**: only semantic tokens (`bg-background`, `text-foreground`, `border-border`, `bg-primary`, etc.)—no raw hex/oklch in JSX or one-off colors unless adding a new token in `src/index.css`.
1919+- [ ] **Radius**: pick the tier from [tokens.md](tokens.md) (`rounded` / `rounded-md` / `rounded-lg` / `rounded-full`) and stay consistent within one surface (e.g. do not mix `rounded` and `rounded-md` on sibling list rows).
2020+- [ ] **Spacing**: icon `size-4`; icon+label `gap-2`; compact chrome `p-3`; roomy panels `p-5`; list rows `px-2 py-1.5` unless a denser pattern is documented.
2121+- [ ] **Focus**: interactive controls get a visible `focus-visible` ring (see [components.md](components.md)); never `outline-none` without a replacement.
2222+- [ ] **Motion**: prefer CSS transitions on a small set of properties; honor `prefers-reduced-motion` (see [motion.md](motion.md)).
2323+- [ ] **Accessibility**: labels for icon-only controls, correct roles for dialogs/switches/lists, hit targets ≥ 44×44px where practical—see [a11y.md](a11y.md).
2424+2525+## Theming model
2626+2727+1. **Theme source**: `:root` and `:root[data-theme='dark']` define `--semantic-*` variables in `src/index.css`.
2828+2. **Tailwind bridge**: `@theme` maps those variables to utilities (`background`, `foreground`, `primary`, `accent`, `border`, `destructive`).
2929+3. **Components**: consume **only** utilities derived from tokens (including opacity modifiers like `text-foreground/60`). Future user themes swap the CSS variables; JSX should not assume fixed colors.
3030+3131+When adding a new conceptual color (e.g. “warning”), add `--semantic-*` in both light and dark blocks, wire it in `@theme`, then use the new utility—do not spread ad-hoc OKLCH across components.
3232+3333+Planned token extensions (`--radius-*`, `--motion-*`, `--font-*`, surface/muted) are specified in [tokens.md](tokens.md) for a follow-up CSS PR; until then, use the Tailwind class conventions listed there.
3434+3535+## Token cheat sheet
3636+3737+| Area | Rule |
3838+|------------|------|
3939+| Page shell | `bg-background text-foreground` |
4040+| Hairlines | `border-border` (1px borders) |
4141+| Primary CTA | `bg-primary text-primary-foreground` |
4242+| Links / key accents in prose | `text-accent` (per `EditPad`) |
4343+| Danger | `text-destructive` for copy; full destructive buttons TBD |
4444+| Muted copy | `text-foreground/60`–`/80` until `--semantic-muted-fg` lands |
4545+4646+Full tables and proposed CSS variables: [tokens.md](tokens.md).
4747+4848+## Motion
4949+5050+Default global transitions (~320ms) already apply to background, color, border, fill, stroke, shadow. Layer **short** transitions for layout chrome (e.g. sidebar width ~200ms). Details and anti-patterns: [motion.md](motion.md).
5151+5252+## Accessibility
5353+5454+Electron uses Chromium: treat the renderer like a **desktop web app** for a11y (keyboard, focus, ARIA, contrast). macOS VoiceOver is the primary screen reader to verify. Electron- and title-bar-specific notes: [a11y.md](a11y.md).
5555+5656+## Component patterns
5757+5858+Canonical snippets and file references: [components.md](components.md).
5959+6060+## Anti-patterns
6161+6262+- Hard-coded colors (`#fff`, `oklch(...)`) in class strings or inline styles for product chrome.
6363+- `transition-all` or animating `width`/`height` on heavy subtrees without a performance pass.
6464+- Icon-only buttons without `aria-label` (decorative icons: `aria-hidden`).
6565+- Removing outlines without an equivalent `focus-visible` style.
6666+- Inconsistent radius or gap within one list or one modal.
6767+6868+## Cursor setup
6969+7070+The committed skill path is `.opencode/skills/textile-design/`. For Cursor, create a symlink (ignored by git): see [../README.md](../README.md).
7171+7272+## Additional resources
7373+7474+- [tokens.md](tokens.md) — semantic colors, proposed radii/motion/fonts, spacing vocabulary.
7575+- [motion.md](motion.md) — durations, easings, reduced motion.
7676+- [a11y.md](a11y.md) — Electron, custom title bar, ARIA recipes, VoiceOver smoke test.
7777+- [components.md](components.md) — patterns and migration notes.
+53
.opencode/skills/textile-design/a11y.md
···11+# Accessibility (Textile, Electron renderer)
22+33+## Mental model
44+55+The renderer is **Chromium**. Follow the same baseline as a SPA:
66+77+- Full **keyboard** operation for chrome, modals, and settings.
88+- Visible **focus** for every focusable control.
99+- Correct **roles** and **ARIA** for dialogs, switches, lists, and tabs (when introduced).
1010+- **Color contrast** for text and icons against `background` / surfaces (WCAG 2.2 AA: 4.5:1 normal text, 3:1 large text/UI graphics where applicable).
1111+1212+Screen reader testing: **VoiceOver on macOS** (primary platform today).
1313+1414+## Electron-specific
1515+1616+- **Window Controls Overlay / custom title bar:** any interactive control in the drag region must use `WebkitAppRegion: 'no-drag'` so clicks reach the button (see `AppTitleBar`). Do not place the only copy of an action inside a pure drag region.
1717+- **Native menus and accelerators:** when features move to the OS menu, keep **accessible names** in the renderer in sync so VoiceOver and keyboard users are not worse off than menu-only users.
1818+- **Modal dialogs:** backdrop + panel should trap focus in the dialog while open, restore focus on close, and block inert content (future hardening—today verify tab order manually).
1919+2020+## Patterns in the codebase
2121+2222+| Pattern | Expectation |
2323+|---------|-------------|
2424+| **Dialog** | `role="dialog"`, `aria-modal="true"`, `aria-label` or labelled-by; Esc to close when appropriate. |
2525+| **Icon-only button** | `aria-label` on `<button>`; `aria-hidden` on decorative `<svg>`. |
2626+| **Toggle / switch** | `role="switch"`, `aria-checked`, `aria-label` (see theme control in `SettingsModal`). |
2727+| **Sidebar toggle** | `aria-controls` pointing at sidebar `id`, `aria-pressed` for open state. |
2828+| **Decorative icons** | `aria-hidden` on SVG when adjacent text conveys meaning. |
2929+| **Lists of actions** | Prefer native list semantics (`role="list"` / `listitem`) or ensure roving tabindex if building composite widgets. |
3030+3131+## Focus rings
3232+3333+Use `focus-visible` (not `focus`) so mouse users do not see a ring on every click. Pair `outline-none` with an explicit ring or outline (see file row button in `AppSidebar`).
3434+3535+## Hit targets
3636+3737+Aim for at least **44×44px** for primary chrome controls where layout allows. If visual size is smaller, expand the interactive padding (`p-1.5` on a `size-4` icon ≈ 32px—acceptable for dense chrome; avoid going tighter without a design pass).
3838+3939+## VoiceOver smoke test (manual)
4040+4141+1. Turn on VoiceOver (Cmd+F5).
4242+2. **Title bar:** toggle sidebar—announce label changes, button remains activatable.
4343+3. **Open Settings** (⌘,): focus lands in dialog; tab cycles **within** dialog; Close works.
4444+4. **Settings sidebar:** category buttons announce state (consider `aria-current="true"` on selected category—future improvement).
4545+5. **Theme switch:** announces as switch; toggling updates `aria-checked`.
4646+6. **Editor:** placeholder and content are readable; focus ring visible in `EditPad`.
4747+4848+## Further reading
4949+5050+- [Electron accessibility](https://www.electronjs.org/docs/latest/tutorial/accessibility)
5151+- [WCAG 2.2](https://www.w3.org/TR/WCAG22/)
5252+5353+Return to [SKILL.md](SKILL.md).
+72
.opencode/skills/textile-design/components.md
···11+# Component patterns (Textile)
22+33+Reference implementations live under `src/components/` unless noted.
44+55+## Modal shell
66+77+**Reference:** `Modal.tsx`
88+99+- Overlay: dim + optional blur; `role="dialog"`, `aria-modal="true"`, `aria-label`.
1010+- Panel: `rounded-lg`, `border-border`, `shadow-xl`, `max-w-[92vw]`.
1111+- Header row: `h-11`, `border-b`, title centered, close control `aria-label="Close modal"`, icon `aria-hidden`.
1212+1313+## Settings layout
1414+1515+**Reference:** `SettingsPane.tsx` — sidebar `w-60`, `p-3`, `border-r`; main `p-5`.
1616+1717+## List row (file / nav)
1818+1919+**Reference:** `AppSidebar.tsx` (file buttons)
2020+2121+- `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm`
2222+- Icon: `size-4 shrink-0`, muted `text-foreground/50`
2323+- Selection: `aria-current="true"`, `bg-border/50`
2424+- Focus: `outline-none focus-visible:ring-2 focus-visible:ring-accent/50`
2525+2626+## Icon-only control (chrome)
2727+2828+**Reference:** `AppTitleBar.tsx` (sidebar toggle), `Modal.tsx` (close)
2929+3030+- Button: `rounded` or `rounded-md` + `p-1` / `p-1.5`
3131+- `aria-label` describing action; `aria-pressed` if toggle
3232+- SVG decorative: `aria-hidden`
3333+3434+## Primary / secondary buttons
3535+3636+**Primary CTA:** `bg-primary text-primary-foreground`, adequate padding (`px-3 py-2`), optional `hover:opacity-90`, `disabled:opacity-50`.
3737+3838+**Secondary / ghost:** `border-border bg-background text-foreground hover:bg-border/30` with `rounded-md` and `border` where shown as a pill-shaped outline button.
3939+4040+**Migrate:** `SettingsModal` “Sign in” uses `rounded` + `text-xs`—consider `rounded-md` + `text-sm` for consistency with inputs and touch targets.
4141+4242+## Form fields
4343+4444+**Reference:** `SettingsModal` account input
4545+4646+- Input: `rounded-md border border-border bg-background px-3 py-2 text-sm`
4747+- Associate `<label htmlFor>` with input `id`
4848+4949+## Editor surface
5050+5151+**Reference:** `EditPad.tsx`
5252+5353+- Outer chrome: `rounded-md border`, `bg-background`, `min-h-0` flex discipline for scroll.
5454+- Prose links: `text-accent`; focus ring uses semantic accent via arbitrary property.
5555+5656+## Theme switch
5757+5858+**Reference:** `SettingsModal.tsx`
5959+6060+- `role="switch"`, `aria-checked`, `aria-label`
6161+- `rounded-full` track; knob uses `transition-transform` + `duration-500` (see [motion.md](motion.md) for reduced-motion considerations).
6262+6363+## Migration / consistency backlog
6464+6565+| Item | Suggestion |
6666+|------|------------|
6767+| Settings category rows (`rounded` vs `rounded-md`) | Use `rounded-md` to match `AppSidebar` rows |
6868+| “Sign in” sizing | Align with `text-sm` + `rounded-md` where it is a primary action |
6969+| Settings category selected state | Add `aria-current="true"` on active category button |
7070+| Modal focus trap | Add roving focus / focus trap library or manual implementation |
7171+7272+Return to [SKILL.md](SKILL.md).
+42
.opencode/skills/textile-design/motion.md
···11+# Motion (Textile)
22+33+## Principles
44+55+- **Calm:** one clear transition per state change; avoid staggered novelty animations on every mount.
66+- **Purpose:** motion explains **where** something went (sidebar collapse, modal open, theme toggle) or **that** state changed—not decoration.
77+- **Performance:** prefer `transform` and `opacity` for high-frequency effects; avoid `transition-all`. Be cautious animating `width` on large content (sidebar uses width but keeps inner width fixed—acceptable pattern).
88+99+## Built-in baseline (`src/index.css`)
1010+1111+Global transition on `*`:
1212+1313+- Properties: `background-color`, `color`, `border-color`, `fill`, `stroke`, `box-shadow`
1414+- Duration: **320ms**
1515+- Easing: `ease-in-out`
1616+- **`prefers-reduced-motion: reduce`:** duration forced to **0ms** for those rules
1717+1818+Any **additional** motion (transform, width, layout) must respect reduced motion: pair with `@media (prefers-reduced-motion: reduce)` and cut duration/transform, or supply an instant alternative.
1919+2020+## Durations in use today
2121+2222+| Surface | Duration | Easing | Location |
2323+|---------|----------|--------|----------|
2424+| Theme colors / borders | 320ms | ease-in-out | Global `@layer base` |
2525+| Sidebar width | 200ms (width), 320ms (colors) | ease-out / ease-in-out | `AppSidebar` inline `style` |
2626+| Theme switch knob | 500ms | ease-in-out | `SettingsModal` `transition-transform` |
2727+2828+Proposed standard names: `--motion-fast` (160ms), `--motion-base` (320ms), `--motion-slow` (500ms)—see [tokens.md](tokens.md).
2929+3030+## Do
3131+3232+- Use **explicit** `transition-property` lists when overriding globals on an element.
3333+- Use **short** durations for spatial layout (sidebars, drawers); keep **320ms** for palette/border changes so theme toggles feel soft, not sluggish.
3434+- Test with macOS “Reduce motion” enabled (System Settings → Accessibility → Display).
3535+3636+## Do not
3737+3838+- `transition-all` (hard to reason about and expensive).
3939+- Long bounce/elastic easing on chrome (reserve personality for rare celebratory moments, if any).
4040+- Animations that block reading or input (no infinite distracting loops in chrome).
4141+4242+Return to [SKILL.md](SKILL.md).
+94
.opencode/skills/textile-design/tokens.md
···11+# Design tokens (Textile)
22+33+## Current semantic colors (`src/index.css`)
44+55+Defined on `:root` and overridden on `:root[data-theme='dark']`, then exposed via `@theme` as Tailwind colors:
66+77+| Variable | Tailwind utilities |
88+|----------|---------------------|
99+| `--semantic-bg` | `bg-background` |
1010+| `--semantic-fg` | `text-foreground` |
1111+| `--semantic-primary` | `bg-primary`, `text-primary`, ring usage as needed |
1212+| `--semantic-primary-fg` | `text-primary-foreground` |
1313+| `--semantic-accent` | `text-accent`, `bg-accent` (if used) |
1414+| `--semantic-border` | `border-border`, hairline dividers |
1515+| `--semantic-destructive` | `text-destructive`, `bg-destructive` (if used) |
1616+1717+**Rule:** components reference **Tailwind utilities**, not `--semantic-*` directly, except inside global CSS or when defining new `@theme` entries.
1818+1919+## Proposed additions (document only — implement in a later PR)
2020+2121+These tighten theming and reduce repeated opacity hacks.
2222+2323+### Surfaces and muted text
2424+2525+```css
2626+:root {
2727+ --semantic-surface: oklch(0.97 0 0); /* example: slightly above bg */
2828+ --semantic-muted-fg: oklch(0.45 0 0);
2929+}
3030+:root[data-theme='dark'] {
3131+ --semantic-surface: oklch(0.22 0.02 260);
3232+ --semantic-muted-fg: oklch(0.72 0.02 260);
3333+}
3434+```
3535+3636+Then map in `@theme` (e.g. `--color-muted-foreground: var(--semantic-muted-fg)`) and migrate `text-foreground/60` call sites gradually.
3737+3838+### Radii (align with existing Tailwind usage)
3939+4040+| Token (proposed) | Value | Tailwind mapping | Use for |
4141+|------------------|-------|------------------|---------|
4242+| `--radius-sm` | 4px | `rounded` | Compact icon hit areas, small chips |
4343+| `--radius-md` | 6px | `rounded-md` | Inputs, list rows, `EditPad` container |
4444+| `--radius-lg` | 10px | `rounded-lg` | Modal panels, large cards |
4545+| — | pill | `rounded-full` | Switches, avatars |
4646+4747+**Consistency rule:** within a single scrollable list or form, use **one** radius tier for sibling controls.
4848+4949+### Motion (proposed CSS variables)
5050+5151+| Token | ms | Notes |
5252+|-------|-----|------|
5353+| `--motion-fast` | 160 | Sidebar width, small positional tweens |
5454+| `--motion-base` | 320 | Matches global transition in `@layer base` today |
5555+| `--motion-slow` | 500 | Theme toggle thumb (`SettingsModal`) |
5656+5757+Wire to Tailwind in a later PR (e.g. `@theme { --duration-base: var(--motion-base); }`) so components use `duration-base` instead of magic numbers.
5858+5959+### Typography (future theme fonts)
6060+6161+```css
6262+:root {
6363+ --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
6464+ --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
6565+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
6666+}
6767+body {
6868+ font-family: var(--font-sans);
6969+}
7070+```
7171+7272+Until this lands, keep the stack in `src/index.css` `body` as the single source of truth.
7373+7474+## Spacing vocabulary (Tailwind, no new tokens)
7575+7676+Use these defaults unless a layout clearly needs an exception:
7777+7878+| Context | Classes |
7979+|---------|---------|
8080+| Icon size (inline, lists) | `size-4` |
8181+| Icon ↔ label | `gap-2` |
8282+| List / nav row | `px-2 py-1.5` |
8383+| Icon button (title bar, modal close) | `p-1` or `p-1.5` with `rounded` |
8484+| Sidebar / tight panels | `p-3`, section gaps `gap-4` |
8585+| Settings main column | `p-5` (`SettingsPaneLayout`) |
8686+| Dense vertical stacks | `space-y-1`–`space-y-4` |
8787+8888+## Migration notes (current codebase)
8989+9090+- **Aligned:** `Modal` uses `rounded-lg`; `AppSidebar` file rows use `rounded-md`, `gap-2`, `size-4`; `EditPad` wrapper uses `rounded-md border`.
9191+- **Unify later:** `SettingsModal` category buttons use `rounded` while file rows use `rounded-md`—pick `rounded-md` for parity (see [components.md](components.md)).
9292+- **Primary actions:** `Sign in` uses `rounded` + `text-xs`; consider `rounded-md` + `text-sm` for touch/readability when touching that flow.
9393+9494+Return to [SKILL.md](SKILL.md).