--- name: worklog-design description: > Design and UI skill for the Worklog desktop project manager. Stack: SvelteKit 5 (Svelte Runes), Tauri v2, carbon-components-svelte, TypeScript, Bun. Use whenever building, refactoring, or extending Worklog's UI — new views, components, layouts, theming, or visual polish aligned with the Carbon Design System and the local-first desktop product philosophy. --- # Worklog Design Skill ## Project Identity Worklog is a **local-first, keyboard-driven desktop project manager** for small dev teams. Core qualities that should always be felt in the UI: | Quality | What it means in practice | |---|---| | **Fast** | Instant feedback. No loading skeletons for local SQLite data. Transitions ≤ 150 ms. | | **Keyboard-first** | Every action reachable without a mouse. Shortcuts visible in tooltips. | | **Dense, not cluttered** | Information-rich layout typical of desktop apps. Prefer compact Carbon sizing. | | **Local-first transparency** | No cloud metaphors. Workspace = folder on disk. | | **Small team focus** | No enterprise complexity. One workspace, one team, clear hierarchy. | --- ## Stack Constraints ### SvelteKit 5 + Svelte Runes - Components use `$state`, `$derived`, `$effect` — no `writable()` stores for new code. - Layouts own scope: workspace scope in `+layout.svelte`, board scope in nested layouts. - Use `$page` from `$app/stores` for route params; prefer typed params via `RouteParams`. - Async data goes in `+page.ts` / `+layout.ts` `load()` functions, not inline `onMount`. - Avoid `onMount` for data fetching; it causes flash-of-empty-content in Tauri webview. ### Tauri v2 - All persistence calls go through Tauri SQL plugin (`@tauri-apps/plugin-sql`) via the repository layer in `src/lib/db/`. - **Never** call the repository directly from a component — always go through a hook in `src/lib/hooks/`. - Tauri `invoke` for custom Rust commands (e.g., Git sync, file system operations). - Window is frameless; the app shell owns the drag region and custom title bar area. - No `localStorage`, `sessionStorage`, or IndexedDB — SQLite is the source of truth. - File paths use Tauri path API (`appDataDir`, `join`) — never hardcode OS paths. ### carbon-components-svelte - Theme: use **Gray 90** (`g90`) dark theme as the default — it matches the existing dark shell. - Import in `app.html` or root `+layout.svelte`: `import 'carbon-components-svelte/css/g90.css'` - For light mode support, dynamically swap to `g10`. - Use `optimizeImports` from `carbon-preprocess-svelte` in `svelte.config.js` to avoid slow dev builds. - Prefer Carbon's compact density tokens: `size="sm"` on buttons/inputs inside panels. - **Do not fight Carbon's CSS custom properties** — override with `--cds-*` tokens, never with `!important` hacks. - Common components to reach for first: | Use case | Carbon component | |---|---| | Sidebar nav | `SideNav`, `SideNavItems`, `SideNavLink`, `SideNavMenu` | | Command palette / search | `Search` + custom modal overlay | | Ticket cards | `Tile`, `ClickableTile` | | Modals / dialogs | `Modal` | | Forms | `TextInput`, `TextArea`, `Select`, `Toggle`, `Checkbox` | | Tags / labels | `Tag` | | Data tables | `DataTable`, `Toolbar`, `ToolbarSearch` | | Notifications | `InlineNotification`, `ToastNotification` | | Buttons | `Button`, `IconButton` | | Context menus | `OverflowMenu`, `OverflowMenuItem` | | Tooltips | `Tooltip`, `TooltipDefinition` | | Progress / loading | `InlineLoading`, `SkeletonText` | --- ## Layout Architecture ``` AppShell (root +layout.svelte) ├── TitleBar ← custom drag region, app name, window controls ├── SideNav ← workspaces list + board tree │ ├── WorkspaceHeader │ ├── BoardTree ← per-board SideNavLink with right-click OverflowMenu │ └── NavFooter ← settings link, sync status badge └── MainContent ← — swapped by nested routes ├── KanbanView ← /boards/[id] ├── TableView ← /boards/[id]/table ├── TimelineView ← /boards/[id]/timeline └── SettingsView ← /settings/[tab] ``` **Key layout rules:** - The sidebar is fixed-width (`240px` default, resizable via CSS variable `--worklog-sidebar-width`). - The title bar drag region must use `-webkit-app-region: drag` with interactive elements set to `-webkit-app-region: no-drag`. - Never use full-page loading states — local SQLite is fast; show stale data instantly, then update. --- ## Design Tokens & Custom Properties Override or extend Carbon tokens at `:root` in `src/app.css`: ```css :root { /* Sidebar */ --worklog-sidebar-width: 240px; --worklog-sidebar-bg: var(--cds-ui-background); /* matches g90 */ /* Kanban columns */ --worklog-column-width: 280px; --worklog-column-gap: 12px; /* Ticket card */ --worklog-card-radius: 2px; /* Carbon is square-ish */ --worklog-card-padding: 12px; /* Status colors — extend Carbon's semantic palette */ --worklog-status-backlog: var(--cds-text-02); --worklog-status-todo: var(--cds-interactive-01); --worklog-status-in-progress: #f1c21b; /* Carbon yellow-30 */ --worklog-status-done: #42be65; /* Carbon green-40 */ /* Priority */ --worklog-priority-low: var(--cds-text-02); --worklog-priority-medium: var(--cds-interactive-01); --worklog-priority-high: #ff832b; /* Carbon orange-40 */ --worklog-priority-critical: var(--cds-support-error); /* Typography — Carbon uses IBM Plex by default; Worklog inherits this */ --worklog-font-mono: 'IBM Plex Mono', monospace; /* Zoom — controlled by app settings (50%–200%) */ --worklog-zoom: 1; } ``` Apply zoom at the app shell level: ```css #app-shell { zoom: var(--worklog-zoom); /* Tauri webview supports this */ } ``` --- ## Component Patterns ### Kanban Board ```svelte
{#each columns as col}
{col.label} {col.tickets.length}
{#each col.tickets as ticket (ticket.id)} {/each}
{/each}
``` ### Ticket Card ```svelte
onSelect(ticket.id)} onkeydown={handleKeydown} >
{ticket.id.slice(0, 8)} onMove(ticket.id, 'next')} /> onMove(ticket.id, 'prev')} />

{ticket.title}

{#if ticket.priority} {ticket.priority} {/if} {#if ticket.due_date} {formatDate(ticket.due_date)} {/if}
``` ### Command Palette Worklog's command palette (`Ctrl/Cmd+K`) should use Carbon's `Search` inside a `Modal`: ```svelte { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); open = true; } }} /> ``` --- ## Interaction & Motion Worklog is a **productivity tool** — motion must be functional, never decorative: - **Transitions**: `transition: background 120ms ease, border-color 120ms ease` for hover/focus states. - **Panel slide-in**: Ticket detail panel should slide in from the right at `200ms ease-out`. Use Svelte's `fly` transition: `transition:fly={{ x: 320, duration: 200 }}`. - **Column drag-and-drop**: Use `dndzone` from `svelte-dnd-action` — it handles accessibility and keyboard DnD natively. - **No skeleton loaders**: Local SQLite data is synchronous-ish. Show last-known state immediately; update reactively. - **Toast notifications**: Use `ToastNotification` for Git sync events (success / failure). Position bottom-right, auto-dismiss after 4 s. --- ## Accessibility (a11y) Carbon handles most a11y out of the box. Worklog-specific requirements: - Every interactive Svelte component that isn't a native button must have `role` + `tabindex="0"` + `onkeydown` handler. - Keyboard shortcut hints: show in `TooltipDefinition` with `align="bottom"`, text like `M — move ticket`. - Focus management: when ticket detail panel opens, move focus to the panel's close button; when it closes, return focus to the triggering card. - Use `aria-live="polite"` on the sync status badge in the sidebar. --- ## File Conventions | File | Purpose | |---|---| | `src/app.css` | Global CSS, custom properties, Carbon theme import | | `src/lib/components/app/` | Domain feature components (KanbanBoard, TicketCard, etc.) | | `src/lib/components/ui/` | Generic reusable components that wrap or extend Carbon | | `src/lib/hooks/` | Svelte 5 Runes hooks — data fetching, mutations, reactive state | | `src/lib/db/` | Repository layer — only place that calls `@tauri-apps/plugin-sql` | | `src/routes/` | SvelteKit routes; layouts own scope | --- ## Do / Don't | ✅ Do | ❌ Don't | |---|---| | Use Carbon tokens (`--cds-*`) for all colors | Use raw hex colors inside components | | Put data access in hooks, not components | Call `db.*` directly from a `.svelte` file | | Use `$state` / `$derived` (Svelte 5 Runes) | Use `writable()` / `readable()` stores | | Reach for Carbon components first | Rebuild what Carbon already provides | | Keep transitions ≤ 200 ms | Add decorative animations or loading spinners | | Use `OverflowMenu` for context actions | Build custom dropdown menus | | Apply `size="sm"` in dense panel areas | Use default (md) sizing everywhere — too tall | | Test keyboard navigation on every new component | Ship mouse-only interactions |