A local-first note taking app
0

Configure Feed

Select the types of activity you want to include in your feed.

Add design skill

Ethan Graf (May 13, 2026, 9:46 PM EDT) efddb808 f69688c6

+352
+3
.gitignore
··· 96 96 .vscode 97 97 98 98 dist 99 + 100 + # Cursor skill symlink (canonical skill lives under .opencode/skills/) 101 + .cursor/skills/textile-design
+11
.opencode/skills/README.md
··· 1 + # Agent skills (OpenCode) 2 + 3 + Skills live in subfolders with `SKILL.md` (see [OpenCode skills docs](https://opencode.ai/docs/skills)). 4 + 5 + **Cursor:** after clone, link the design skill so Cursor discovers it: 6 + 7 + ```bash 8 + mkdir -p .cursor/skills && ln -sf ../../.opencode/skills/textile-design .cursor/skills/textile-design 9 + ``` 10 + 11 + Run from the repository root (`textile/`).
+77
.opencode/skills/textile-design/SKILL.md
··· 1 + --- 2 + name: textile-design 3 + 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/. 4 + --- 5 + 6 + # Textile design 7 + 8 + ## Philosophy 9 + 10 + 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. 11 + 12 + 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. 13 + 14 + ## Before you ship UI 15 + 16 + Use this checklist on every new or changed surface: 17 + 18 + - [ ] **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`. 19 + - [ ] **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). 20 + - [ ] **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. 21 + - [ ] **Focus**: interactive controls get a visible `focus-visible` ring (see [components.md](components.md)); never `outline-none` without a replacement. 22 + - [ ] **Motion**: prefer CSS transitions on a small set of properties; honor `prefers-reduced-motion` (see [motion.md](motion.md)). 23 + - [ ] **Accessibility**: labels for icon-only controls, correct roles for dialogs/switches/lists, hit targets ≥ 44×44px where practical—see [a11y.md](a11y.md). 24 + 25 + ## Theming model 26 + 27 + 1. **Theme source**: `:root` and `:root[data-theme='dark']` define `--semantic-*` variables in `src/index.css`. 28 + 2. **Tailwind bridge**: `@theme` maps those variables to utilities (`background`, `foreground`, `primary`, `accent`, `border`, `destructive`). 29 + 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. 30 + 31 + 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. 32 + 33 + 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. 34 + 35 + ## Token cheat sheet 36 + 37 + | Area | Rule | 38 + |------------|------| 39 + | Page shell | `bg-background text-foreground` | 40 + | Hairlines | `border-border` (1px borders) | 41 + | Primary CTA | `bg-primary text-primary-foreground` | 42 + | Links / key accents in prose | `text-accent` (per `EditPad`) | 43 + | Danger | `text-destructive` for copy; full destructive buttons TBD | 44 + | Muted copy | `text-foreground/60`–`/80` until `--semantic-muted-fg` lands | 45 + 46 + Full tables and proposed CSS variables: [tokens.md](tokens.md). 47 + 48 + ## Motion 49 + 50 + 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). 51 + 52 + ## Accessibility 53 + 54 + 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). 55 + 56 + ## Component patterns 57 + 58 + Canonical snippets and file references: [components.md](components.md). 59 + 60 + ## Anti-patterns 61 + 62 + - Hard-coded colors (`#fff`, `oklch(...)`) in class strings or inline styles for product chrome. 63 + - `transition-all` or animating `width`/`height` on heavy subtrees without a performance pass. 64 + - Icon-only buttons without `aria-label` (decorative icons: `aria-hidden`). 65 + - Removing outlines without an equivalent `focus-visible` style. 66 + - Inconsistent radius or gap within one list or one modal. 67 + 68 + ## Cursor setup 69 + 70 + The committed skill path is `.opencode/skills/textile-design/`. For Cursor, create a symlink (ignored by git): see [../README.md](../README.md). 71 + 72 + ## Additional resources 73 + 74 + - [tokens.md](tokens.md) — semantic colors, proposed radii/motion/fonts, spacing vocabulary. 75 + - [motion.md](motion.md) — durations, easings, reduced motion. 76 + - [a11y.md](a11y.md) — Electron, custom title bar, ARIA recipes, VoiceOver smoke test. 77 + - [components.md](components.md) — patterns and migration notes.
+53
.opencode/skills/textile-design/a11y.md
··· 1 + # Accessibility (Textile, Electron renderer) 2 + 3 + ## Mental model 4 + 5 + The renderer is **Chromium**. Follow the same baseline as a SPA: 6 + 7 + - Full **keyboard** operation for chrome, modals, and settings. 8 + - Visible **focus** for every focusable control. 9 + - Correct **roles** and **ARIA** for dialogs, switches, lists, and tabs (when introduced). 10 + - **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). 11 + 12 + Screen reader testing: **VoiceOver on macOS** (primary platform today). 13 + 14 + ## Electron-specific 15 + 16 + - **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. 17 + - **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. 18 + - **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). 19 + 20 + ## Patterns in the codebase 21 + 22 + | Pattern | Expectation | 23 + |---------|-------------| 24 + | **Dialog** | `role="dialog"`, `aria-modal="true"`, `aria-label` or labelled-by; Esc to close when appropriate. | 25 + | **Icon-only button** | `aria-label` on `<button>`; `aria-hidden` on decorative `<svg>`. | 26 + | **Toggle / switch** | `role="switch"`, `aria-checked`, `aria-label` (see theme control in `SettingsModal`). | 27 + | **Sidebar toggle** | `aria-controls` pointing at sidebar `id`, `aria-pressed` for open state. | 28 + | **Decorative icons** | `aria-hidden` on SVG when adjacent text conveys meaning. | 29 + | **Lists of actions** | Prefer native list semantics (`role="list"` / `listitem`) or ensure roving tabindex if building composite widgets. | 30 + 31 + ## Focus rings 32 + 33 + 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`). 34 + 35 + ## Hit targets 36 + 37 + 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). 38 + 39 + ## VoiceOver smoke test (manual) 40 + 41 + 1. Turn on VoiceOver (Cmd+F5). 42 + 2. **Title bar:** toggle sidebar—announce label changes, button remains activatable. 43 + 3. **Open Settings** (⌘,): focus lands in dialog; tab cycles **within** dialog; Close works. 44 + 4. **Settings sidebar:** category buttons announce state (consider `aria-current="true"` on selected category—future improvement). 45 + 5. **Theme switch:** announces as switch; toggling updates `aria-checked`. 46 + 6. **Editor:** placeholder and content are readable; focus ring visible in `EditPad`. 47 + 48 + ## Further reading 49 + 50 + - [Electron accessibility](https://www.electronjs.org/docs/latest/tutorial/accessibility) 51 + - [WCAG 2.2](https://www.w3.org/TR/WCAG22/) 52 + 53 + Return to [SKILL.md](SKILL.md).
+72
.opencode/skills/textile-design/components.md
··· 1 + # Component patterns (Textile) 2 + 3 + Reference implementations live under `src/components/` unless noted. 4 + 5 + ## Modal shell 6 + 7 + **Reference:** `Modal.tsx` 8 + 9 + - Overlay: dim + optional blur; `role="dialog"`, `aria-modal="true"`, `aria-label`. 10 + - Panel: `rounded-lg`, `border-border`, `shadow-xl`, `max-w-[92vw]`. 11 + - Header row: `h-11`, `border-b`, title centered, close control `aria-label="Close modal"`, icon `aria-hidden`. 12 + 13 + ## Settings layout 14 + 15 + **Reference:** `SettingsPane.tsx` — sidebar `w-60`, `p-3`, `border-r`; main `p-5`. 16 + 17 + ## List row (file / nav) 18 + 19 + **Reference:** `AppSidebar.tsx` (file buttons) 20 + 21 + - `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm` 22 + - Icon: `size-4 shrink-0`, muted `text-foreground/50` 23 + - Selection: `aria-current="true"`, `bg-border/50` 24 + - Focus: `outline-none focus-visible:ring-2 focus-visible:ring-accent/50` 25 + 26 + ## Icon-only control (chrome) 27 + 28 + **Reference:** `AppTitleBar.tsx` (sidebar toggle), `Modal.tsx` (close) 29 + 30 + - Button: `rounded` or `rounded-md` + `p-1` / `p-1.5` 31 + - `aria-label` describing action; `aria-pressed` if toggle 32 + - SVG decorative: `aria-hidden` 33 + 34 + ## Primary / secondary buttons 35 + 36 + **Primary CTA:** `bg-primary text-primary-foreground`, adequate padding (`px-3 py-2`), optional `hover:opacity-90`, `disabled:opacity-50`. 37 + 38 + **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. 39 + 40 + **Migrate:** `SettingsModal` “Sign in” uses `rounded` + `text-xs`—consider `rounded-md` + `text-sm` for consistency with inputs and touch targets. 41 + 42 + ## Form fields 43 + 44 + **Reference:** `SettingsModal` account input 45 + 46 + - Input: `rounded-md border border-border bg-background px-3 py-2 text-sm` 47 + - Associate `<label htmlFor>` with input `id` 48 + 49 + ## Editor surface 50 + 51 + **Reference:** `EditPad.tsx` 52 + 53 + - Outer chrome: `rounded-md border`, `bg-background`, `min-h-0` flex discipline for scroll. 54 + - Prose links: `text-accent`; focus ring uses semantic accent via arbitrary property. 55 + 56 + ## Theme switch 57 + 58 + **Reference:** `SettingsModal.tsx` 59 + 60 + - `role="switch"`, `aria-checked`, `aria-label` 61 + - `rounded-full` track; knob uses `transition-transform` + `duration-500` (see [motion.md](motion.md) for reduced-motion considerations). 62 + 63 + ## Migration / consistency backlog 64 + 65 + | Item | Suggestion | 66 + |------|------------| 67 + | Settings category rows (`rounded` vs `rounded-md`) | Use `rounded-md` to match `AppSidebar` rows | 68 + | “Sign in” sizing | Align with `text-sm` + `rounded-md` where it is a primary action | 69 + | Settings category selected state | Add `aria-current="true"` on active category button | 70 + | Modal focus trap | Add roving focus / focus trap library or manual implementation | 71 + 72 + Return to [SKILL.md](SKILL.md).
+42
.opencode/skills/textile-design/motion.md
··· 1 + # Motion (Textile) 2 + 3 + ## Principles 4 + 5 + - **Calm:** one clear transition per state change; avoid staggered novelty animations on every mount. 6 + - **Purpose:** motion explains **where** something went (sidebar collapse, modal open, theme toggle) or **that** state changed—not decoration. 7 + - **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). 8 + 9 + ## Built-in baseline (`src/index.css`) 10 + 11 + Global transition on `*`: 12 + 13 + - Properties: `background-color`, `color`, `border-color`, `fill`, `stroke`, `box-shadow` 14 + - Duration: **320ms** 15 + - Easing: `ease-in-out` 16 + - **`prefers-reduced-motion: reduce`:** duration forced to **0ms** for those rules 17 + 18 + 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. 19 + 20 + ## Durations in use today 21 + 22 + | Surface | Duration | Easing | Location | 23 + |---------|----------|--------|----------| 24 + | Theme colors / borders | 320ms | ease-in-out | Global `@layer base` | 25 + | Sidebar width | 200ms (width), 320ms (colors) | ease-out / ease-in-out | `AppSidebar` inline `style` | 26 + | Theme switch knob | 500ms | ease-in-out | `SettingsModal` `transition-transform` | 27 + 28 + Proposed standard names: `--motion-fast` (160ms), `--motion-base` (320ms), `--motion-slow` (500ms)—see [tokens.md](tokens.md). 29 + 30 + ## Do 31 + 32 + - Use **explicit** `transition-property` lists when overriding globals on an element. 33 + - Use **short** durations for spatial layout (sidebars, drawers); keep **320ms** for palette/border changes so theme toggles feel soft, not sluggish. 34 + - Test with macOS “Reduce motion” enabled (System Settings → Accessibility → Display). 35 + 36 + ## Do not 37 + 38 + - `transition-all` (hard to reason about and expensive). 39 + - Long bounce/elastic easing on chrome (reserve personality for rare celebratory moments, if any). 40 + - Animations that block reading or input (no infinite distracting loops in chrome). 41 + 42 + Return to [SKILL.md](SKILL.md).
+94
.opencode/skills/textile-design/tokens.md
··· 1 + # Design tokens (Textile) 2 + 3 + ## Current semantic colors (`src/index.css`) 4 + 5 + Defined on `:root` and overridden on `:root[data-theme='dark']`, then exposed via `@theme` as Tailwind colors: 6 + 7 + | Variable | Tailwind utilities | 8 + |----------|---------------------| 9 + | `--semantic-bg` | `bg-background` | 10 + | `--semantic-fg` | `text-foreground` | 11 + | `--semantic-primary` | `bg-primary`, `text-primary`, ring usage as needed | 12 + | `--semantic-primary-fg` | `text-primary-foreground` | 13 + | `--semantic-accent` | `text-accent`, `bg-accent` (if used) | 14 + | `--semantic-border` | `border-border`, hairline dividers | 15 + | `--semantic-destructive` | `text-destructive`, `bg-destructive` (if used) | 16 + 17 + **Rule:** components reference **Tailwind utilities**, not `--semantic-*` directly, except inside global CSS or when defining new `@theme` entries. 18 + 19 + ## Proposed additions (document only — implement in a later PR) 20 + 21 + These tighten theming and reduce repeated opacity hacks. 22 + 23 + ### Surfaces and muted text 24 + 25 + ```css 26 + :root { 27 + --semantic-surface: oklch(0.97 0 0); /* example: slightly above bg */ 28 + --semantic-muted-fg: oklch(0.45 0 0); 29 + } 30 + :root[data-theme='dark'] { 31 + --semantic-surface: oklch(0.22 0.02 260); 32 + --semantic-muted-fg: oklch(0.72 0.02 260); 33 + } 34 + ``` 35 + 36 + Then map in `@theme` (e.g. `--color-muted-foreground: var(--semantic-muted-fg)`) and migrate `text-foreground/60` call sites gradually. 37 + 38 + ### Radii (align with existing Tailwind usage) 39 + 40 + | Token (proposed) | Value | Tailwind mapping | Use for | 41 + |------------------|-------|------------------|---------| 42 + | `--radius-sm` | 4px | `rounded` | Compact icon hit areas, small chips | 43 + | `--radius-md` | 6px | `rounded-md` | Inputs, list rows, `EditPad` container | 44 + | `--radius-lg` | 10px | `rounded-lg` | Modal panels, large cards | 45 + | — | pill | `rounded-full` | Switches, avatars | 46 + 47 + **Consistency rule:** within a single scrollable list or form, use **one** radius tier for sibling controls. 48 + 49 + ### Motion (proposed CSS variables) 50 + 51 + | Token | ms | Notes | 52 + |-------|-----|------| 53 + | `--motion-fast` | 160 | Sidebar width, small positional tweens | 54 + | `--motion-base` | 320 | Matches global transition in `@layer base` today | 55 + | `--motion-slow` | 500 | Theme toggle thumb (`SettingsModal`) | 56 + 57 + Wire to Tailwind in a later PR (e.g. `@theme { --duration-base: var(--motion-base); }`) so components use `duration-base` instead of magic numbers. 58 + 59 + ### Typography (future theme fonts) 60 + 61 + ```css 62 + :root { 63 + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; 64 + --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; 65 + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; 66 + } 67 + body { 68 + font-family: var(--font-sans); 69 + } 70 + ``` 71 + 72 + Until this lands, keep the stack in `src/index.css` `body` as the single source of truth. 73 + 74 + ## Spacing vocabulary (Tailwind, no new tokens) 75 + 76 + Use these defaults unless a layout clearly needs an exception: 77 + 78 + | Context | Classes | 79 + |---------|---------| 80 + | Icon size (inline, lists) | `size-4` | 81 + | Icon ↔ label | `gap-2` | 82 + | List / nav row | `px-2 py-1.5` | 83 + | Icon button (title bar, modal close) | `p-1` or `p-1.5` with `rounded` | 84 + | Sidebar / tight panels | `p-3`, section gaps `gap-4` | 85 + | Settings main column | `p-5` (`SettingsPaneLayout`) | 86 + | Dense vertical stacks | `space-y-1`–`space-y-4` | 87 + 88 + ## Migration notes (current codebase) 89 + 90 + - **Aligned:** `Modal` uses `rounded-lg`; `AppSidebar` file rows use `rounded-md`, `gap-2`, `size-4`; `EditPad` wrapper uses `rounded-md border`. 91 + - **Unify later:** `SettingsModal` category buttons use `rounded` while file rows use `rounded-md`—pick `rounded-md` for parity (see [components.md](components.md)). 92 + - **Primary actions:** `Sign in` uses `rounded` + `text-xs`; consider `rounded-md` + `text-sm` for touch/readability when touching that flow. 93 + 94 + Return to [SKILL.md](SKILL.md).