From 93af4e6bab881cefdf44c21b514d26d37bb05fe3 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 8 Apr 2026 09:47:11 -0400 Subject: [PATCH 01/11] docs: add Knowledge view task consolidation design spec --- ...2026-04-08-knowledge-task-consolidation.md | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-08-knowledge-task-consolidation.md diff --git a/docs/superpowers/specs/2026-04-08-knowledge-task-consolidation.md b/docs/superpowers/specs/2026-04-08-knowledge-task-consolidation.md new file mode 100644 index 0000000..d0d40a8 --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-knowledge-task-consolidation.md @@ -0,0 +1,144 @@ +# Knowledge View Task Consolidation — Design Spec + +## Goal + +Consolidate tasks into the Knowledge view as a card type, deprecate the standalone `/notes` and `/tasks` list views, and simplify navigation. The Knowledge view becomes the single hub for all content types: notes, tasks, people, places, and lists. + +## Architecture + +The Knowledge view already renders notes, people, places, and lists as typed cards in a filterable grid with a sidebar. Tasks are added as a fifth card type using the same two-tier pagination system (ID pre-fetch → content batch). The backend knowledge endpoints (`/api/knowledge/ids`, `/api/knowledge/batch`, `/api/knowledge/counts`) are extended to include tasks. No changes to the note/task CRUD API. + +## Task Cards + +Task cards follow the same layout as other knowledge cards: + +- **Left accent strip**: distinct color for tasks (e.g. `#a78bfa` purple to differentiate from note indigo) +- **Type badge**: "Task" in top-right corner +- **Card body**: + - Title (2-line clamp) + - Status badge: `todo` / `in_progress` / `done` / `cancelled` — styled with existing status colors from theme (`--color-status-*`) + - Priority indicator: shown only when priority is not `none` — uses existing priority colors (`--color-priority-*`) + - Due date: shown when set, with overdue styling (`--color-overdue`) when past and status is not `done`/`cancelled` +- **Card footer**: tags (up to 3) + last-modified date — identical to other card types + +Clicking a task card navigates to `/tasks/:id/edit` (same as today). + +## Filter Sidebar Changes + +The type filter section gains a "Tasks" button: + +``` +Type +────────── +[All] 127 +[Notes] 84 +[Tasks] 22 +[People] 8 +[Places] 5 +[Lists] 8 +``` + +The filter value for tasks is `type=task`. The backend already stores tasks as notes with `is_task=True`; the knowledge endpoints need to map the `type=task` filter to `is_task=True`. + +## New Note Button Interaction + +Current: click "New note" to create a note; chevron expands a dropdown with Note/Person/Place/List. + +New behavior: + +1. **Click "New note"** (when collapsed) → expands to reveal type options: Task, Person, Place, List. The main button label does not change. +2. **Click "New note"** again (when expanded) → navigates to `/notes/new` (generic note). +3. **Click any type option** → navigates to `/notes/new?type=` (for task: `/notes/new?type=task`, which is equivalent to `/tasks/new`). +4. **Click outside** → collapses the dropdown. + +This replaces the current chevron split-button pattern with a simpler toggle. The dropdown items are: Task, Person, Place, List (no "Note" item in the dropdown — clicking the button itself creates a note). + +## Route Changes + +### Redirects + +| Old route | New behavior | +|-----------|-------------| +| `/notes` | 302 redirect → `/` (Knowledge view) | +| `/tasks` | 302 redirect → `/` (Knowledge view) | + +### Preserved routes (no change) + +| Route | Purpose | +|-------|---------| +| `/notes/:id` | Note viewer | +| `/notes/:id/edit` | Note editor | +| `/notes/new` | New note (with optional `?type=` param) | +| `/tasks/:id/edit` | Task editor | +| `/tasks/new` | New task | + +### Router implementation + +Add redirect entries in the router config: + +```ts +{ path: '/notes', redirect: '/' }, +{ path: '/tasks', redirect: '/' }, +``` + +### Navigation + +Remove from `AppHeader.vue`: +- "Tasks" nav link (``) +- The `/tasks` entry in both desktop nav-center and mobile menu + +Remove from `AppHeader.vue` (already done — `/notes` was removed in a prior change, but verify). + +### Deleted files + +- `frontend/src/views/NotesListView.vue` +- `frontend/src/views/TasksListView.vue` +- `frontend/src/stores/notes.ts` (if only used by NotesListView) +- `frontend/src/stores/tasks.ts` (if only used by TasksListView) + +Verify no other components import from these before deleting. The note/task viewer and editor screens import from `api/client.ts` directly, not from the list stores. + +## Backend Changes + +### `/api/knowledge/ids` + +Accept `type=task` as a valid filter. When `type=task`, query `notes` table with `is_task = True`. When `type` is not set (all), include tasks in results alongside notes/people/places/lists. + +### `/api/knowledge/batch` + +Return task-specific fields for items where `is_task = True`: +- `status`: todo / in_progress / done / cancelled +- `priority`: none / low / normal / high +- `due_date`: ISO date string or null + +These are already columns on the `Note` model — just include them in the batch response when the item is a task. + +### `/api/knowledge/counts` + +Add `task` to the counts response: + +```json +{ "note": 84, "task": 22, "person": 8, "place": 5, "list": 8, "total": 127 } +``` + +### `/api/knowledge/tags` + +No change — tasks already have tags on the same `Note` model. + +## Keyboard Shortcuts + +Remove from `App.vue` `onGlobalKeydown`: +- `case "t": router.push("/tasks/new")` — keep this, it still works +- `case "g"` sequence `case "t": router.push("/tasks")` — change to `router.push("/")` (direct navigation, don't rely on redirect) + +Update shortcuts overlay panel text if it references "Tasks list". + +## No API Endpoint Changes + +All existing REST endpoints remain: +- `GET/POST /api/notes` — notes CRUD +- `GET/POST /api/tasks` — tasks CRUD +- `PATCH /api/notes/:id`, `PATCH /api/tasks/:id` +- `DELETE /api/notes/:id`, `DELETE /api/tasks/:id` + +MCP tools (`fable_create_task`, `fable_list_tasks`, etc.) are unaffected. From 0afa2d877175887bef07a8565ae7be169db55784 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 8 Apr 2026 10:04:55 -0400 Subject: [PATCH 02/11] docs: add Modern Fable visual identity design spec --- ...2026-04-08-visual-identity-modern-fable.md | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-08-visual-identity-modern-fable.md diff --git a/docs/superpowers/specs/2026-04-08-visual-identity-modern-fable.md b/docs/superpowers/specs/2026-04-08-visual-identity-modern-fable.md new file mode 100644 index 0000000..8acc7ac --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-visual-identity-modern-fable.md @@ -0,0 +1,249 @@ +# Modern Fable — Visual Identity Design Spec + +## Goal + +Replace the generic "competent dark-mode Vue app" aesthetic with a distinctive visual identity that is unmistakably Fabled Assistant. The design language evolves from "Illuminated Transcript" to "Modern Fable" — keeping the scholarly DNA but adding personality through color, typography, interaction, and card design that no other app has. + +## Color Palette + +Shift from indigo (`#6366f1`) to deep violet + muted gold. + +### Dark theme + +| Role | Old | New | Usage | +|------|-----|-----|-------| +| Primary | `#818cf8` | `#a78bfa` | Text accents, active states, tags, links | +| Primary solid | `#6366f1` | `#7c3aed` | Buttons, gradients, accent strips | +| Primary deep | `#4f46e5` | `#5b21b6` | Gradient endpoints, hover states | +| Accent (warm) | — | `#d4a017` | Due dates, event times, counts, temporal data | +| Accent light | — | `#e8c45a` | Amber hover states | +| Background | `#111113` | `#0f0f14` | Slightly deeper, more dramatic | +| Surface | `#1a1b22` | `#16161f` | Cards, panels | +| Card bg | `#1e1e27` | `#1a1a24` | Card interiors | +| Border | `rgba(99,102,241,0.10)` | `rgba(124,58,237,0.12)` | Violet-tinted borders | +| Text | `#e4e4f0` | `#e4e4f0` | Unchanged | +| Text muted | `#52526a` | `#52526a` | Unchanged | + +### Light theme + +| Role | Old | New | +|------|-----|-----| +| Primary | `#6366f1` | `#7c3aed` | +| Primary text | `#4f46e5` | `#5b21b6` | +| Accent | — | `#b8860b` (darker gold for light bg) | +| Tag bg | `#ede9fe` | `#ede5ff` | +| Tag text | `#4f46e5` | `#6d28d9` | + +### Semantic color rules + +- **Violet = structural** — navigation, type badges, status indicators, card accents, CTA buttons +- **Amber/gold = temporal** — due dates, event times, countdown values, "overdue" states, calendar dot, relative timestamps +- This duality is a core brand principle: violet organizes, amber marks time + +### Logo update + +Update `AppLogo.vue` SVG fill to use the new violet gradient (`#7c3aed` → `#5b21b6`) instead of the current indigo values. + +## Card Design — Type DNA + +Each content type gets a distinct visual signature recognizable at a glance without reading the badge. + +### Shared card structure + +- Background: `var(--color-surface)` +- Border: `1px solid` with type-tinted color at low opacity +- Border-radius: `var(--radius-lg)` (14px) +- Padding: 14px +- Hover: translateY(-2px) + violet shadow bloom (`0 8px 24px rgba(124,58,237,0.15)`) + +### Type-specific signatures + +**Note** (`note`) +- Top edge: full-width 3px gradient bar (`#7c3aed` → `#a78bfa`) +- Border tint: `rgba(124,58,237,0.12)` +- Badge color: `#a78bfa` + +**Task** (`task`) +- Top edge: half-width 3px gradient bar (`#d4a017` → transparent`), left-aligned — partial bar suggests "in progress" +- Border tint: `rgba(212,160,23,0.10)` +- Badge color: `#d4a017` +- Status badge inline with type badge row +- Due date in amber; overdue in `--color-overdue` (red) + +**Person** (`person`) +- Top edge: none +- Corner accent: subtle 60px quarter-circle in top-right (`rgba(16,185,129,0.06)`) +- Border tint: `rgba(16,185,129,0.10)` +- Badge color: `#34d399` + +**Place** (`place`) +- Top edge: none +- Corner accent: subtle 60px quarter-circle in top-right (`rgba(245,158,11,0.06)`) +- Border tint: `rgba(245,158,11,0.10)` +- Badge color: `#fbbf24` + +**List** (`list`) +- Top edge: full-width 3px gradient bar (`#38bdf8` → `#7dd3fc`) +- Border tint: `rgba(56,189,248,0.10)` +- Badge color: `#7dd3fc` +- Progress bar beneath checkboxes + +### Card hover state + +All cards share the same hover treatment: +```css +.k-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(124,58,237,0.15), 0 2px 8px rgba(0,0,0,0.3); + border-color: rgba(124,58,237,0.2); +} +``` + +## Navigation — Signature Header + +Replace the flat nav links with a pill-grouped tab bar. + +### Structure + +``` +[Logo + "Fabled"] [ Knowledge | Chat | Briefing | Calendar | News | Projects ] [status · ? · ☀ · ⚙ · user] +``` + +### Brand in header + +- Logo: `AppLogo` SVG at 28px with new violet gradient +- Text: "Fabled" only (not "Fabled Assistant") — Fraunces italic, `#c4b0f0`, 15px +- The full name "Fabled Assistant" appears on the login page and Settings; the header uses the short form + +### Tab bar + +- Container: `rgba(124,58,237,0.06)` background, `border-radius: 10px`, 3px padding +- Inactive tabs: transparent background, `color: var(--color-text-muted)` +- Active tab: `rgba(124,58,237,0.2)` background, `border-radius: 8px`, `color: #c4b5fd`, soft box-shadow glow `0 0 12px rgba(124,58,237,0.2)` +- Hover (inactive): `rgba(124,58,237,0.08)` background +- Transition: background 0.15s, color 0.15s + +### Header background + +Subtle gradient: `linear-gradient(180deg, var(--color-surface), var(--color-bg))` with a bottom border of `rgba(124,58,237,0.08)`. Creates depth without being heavy. + +### Mobile + +On mobile (< 768px), the pill bar collapses into the existing hamburger dropdown menu. The dropdown gets the same violet active styling. + +## Typography — Fraunces as Narrator + +Fraunces italic becomes the "narrator's voice" of the application — the assistant speaking through the UI. System UI font remains for body text and interactive elements. + +### Where Fraunces is used + +| Element | Style | Example | +|---------|-------|---------| +| View titles | Fraunces italic, 20-24px, `#c4b0f0` | *Knowledge*, *Chat*, *Briefing* | +| Sidebar section labels | Fraunces italic, 11px, `var(--color-primary)` | *Filter*, *Tags*, *Sort* | +| Empty states | Fraunces italic, 13-15px, `#d4a017` | *"Every story starts with a blank page."* | +| Card headings (h1/h2/h3) | Fraunces, non-italic, 600 weight | Existing behavior, unchanged | +| Briefing greeting | Fraunces italic, 16px | *"Good morning, Bryan"* | + +### Where Fraunces is NOT used + +- Navigation tab labels (system font, 12-13px) +- Buttons and form labels +- Card body text, snippets, metadata +- Toast messages, error text + +### Empty state voice + +Each major view gets a distinctive empty state message in Fraunces italic, amber color: +- Knowledge: *"Your story is unwritten. Create your first note to begin."* +- Chat: *"Start a conversation."* +- Calendar: *"No events ahead. A quiet chapter."* +- Briefing (no briefing yet): *"Your daily briefing will appear here each morning."* + +## Living Details + +Small touches that accumulate into a distinctive feel. + +### Glow interactions + +- **Buttons**: Primary buttons (`btn-send`, `btn-new-note`, CTAs) get a violet glow on hover: `box-shadow: 0 0 16px rgba(124,58,237,0.35)` +- **Focus ring**: Change from current `color-mix` to a violet glow: `0 0 0 2px rgba(124,58,237,0.4)` +- **Active nav tab**: Soft glow behind the active pill (see Navigation section) + +### Amber for temporal data + +Consistently use `#d4a017` (dark theme) for all time-related information: +- Due dates on task cards +- Event times on calendar chips +- "3d ago" timestamps on cards +- Overdue badge in the today bar +- Countdown/relative time in briefing + +This creates a visual language: when you see amber, it's about *when*. + +### Card hover bloom + +Cards lift and emit a violet shadow on hover (see Card Design section). The shadow color matches the card's type accent at very low opacity for a subtle differentiation. + +### Status dot pulse + +The Ollama status indicator in the header gains a CSS pulse animation when the model is loaded: +```css +@keyframes status-pulse { + 0%, 100% { box-shadow: 0 0 4px rgba(74,222,128,0.4); } + 50% { box-shadow: 0 0 10px rgba(74,222,128,0.6); } +} +``` +Pulse only when status is "loaded" (green). Offline (red) and loading (amber) are static. + +### Scroll edge fades + +Top and bottom edges of scrollable areas (card grid, chat messages, sidebar tag list) get a gradient mask that fades content into the background. 20px height, using `mask-image: linear-gradient(...)`. + +### Sidebar section dividers + +Replace flat `border-bottom` between filter sections with a centered ornamental divider: +```css +.filter-section + .filter-section::before { + content: '·'; + display: block; + text-align: center; + color: rgba(124,58,237,0.3); + font-size: 1.2rem; + letter-spacing: 0.5em; + padding: 8px 0; +} +``` +Three centered dots (` · · · `) in faint violet. Subtle but distinctive. + +### Scrollbar + +Keep the current thin scrollbar but update the color from indigo to violet: +```css +::-webkit-scrollbar-thumb { + background: rgba(124,58,237,0.25); +} +``` + +## Files Changed + +| File | Change | +|------|--------| +| `frontend/src/assets/theme.css` | Full palette update (both light and dark), scrollbar color | +| `frontend/src/components/AppLogo.vue` | SVG fill gradient update | +| `frontend/src/components/AppHeader.vue` | Pill-grouped nav tabs, brand shortening, header gradient, status pulse | +| `frontend/src/views/KnowledgeView.vue` | Card type DNA (gradient bars, corner accents), hover bloom, section dividers, empty state text, scroll fades, Fraunces view title | +| `frontend/src/components/ChatPanel.vue` | Scroll fade on messages, empty state text | +| `frontend/src/views/CalendarView.vue` | Empty state text, amber event times | +| `frontend/src/views/BriefingView.vue` | Empty state text, Fraunces greeting | +| `frontend/src/views/ChatView.vue` | (uses ChatPanel — inherits changes) | +| `frontend/src/App.vue` | Update any global styles referencing old indigo values | + +## What Does NOT Change + +- Overall layout structure (sidebar + content + optional graph panel) +- Chat bubble design (user transparent, assistant border-left + shadow) +- TipTap editor styling +- Settings view layout +- Backend — zero changes +- Mobile layout patterns From 0bd362f582e404c001b093f535c06be0b5eb30cc Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 8 Apr 2026 10:11:25 -0400 Subject: [PATCH 03/11] docs: add Knowledge task consolidation implementation plan --- ...2026-04-08-knowledge-task-consolidation.md | 746 ++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-08-knowledge-task-consolidation.md diff --git a/docs/superpowers/plans/2026-04-08-knowledge-task-consolidation.md b/docs/superpowers/plans/2026-04-08-knowledge-task-consolidation.md new file mode 100644 index 0000000..1ee0d6c --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-knowledge-task-consolidation.md @@ -0,0 +1,746 @@ +# Knowledge View Task Consolidation — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Consolidate tasks into the Knowledge view as a fifth card type, deprecate `/notes` and `/tasks` list routes, and simplify navigation down to a single Knowledge hub. + +**Architecture:** The backend knowledge service (`services/knowledge.py`) stops excluding tasks from queries and adds `type=task` filtering via the `is_task` property (`Note.status IS NOT NULL`). The knowledge route validation gains `"task"` as a valid type. The frontend KnowledgeView gains task card rendering with status/priority/due-date badges. Router redirects replace the deleted list views. + +**Tech Stack:** Python/Quart backend (SQLAlchemy), Vue 3 + TypeScript frontend, Pinia stores, Vue Router. + +--- + +## File Map + +| Action | Path | +|--------|------| +| Modify | `src/fabledassistant/services/knowledge.py` | +| Modify | `src/fabledassistant/routes/knowledge.py` | +| Modify | `frontend/src/views/KnowledgeView.vue` | +| Modify | `frontend/src/router/index.ts` | +| Modify | `frontend/src/components/AppHeader.vue` | +| Modify | `frontend/src/App.vue` | +| Delete | `frontend/src/views/NotesListView.vue` | +| Delete | `frontend/src/views/TasksListView.vue` | + +--- + +### Task 1: Backend — Include tasks in knowledge queries + +**Files:** +- Modify: `src/fabledassistant/services/knowledge.py` +- Modify: `src/fabledassistant/routes/knowledge.py` + +**Context:** The knowledge service currently excludes tasks by filtering `Note.status.is_(None)`. Every query function (`query_knowledge`, `query_knowledge_ids`, `_semantic_knowledge_search`, `get_knowledge_tags`, `get_knowledge_counts`) has this exclusion. Adding task support means: (1) removing the task exclusion from the "all types" queries, (2) adding `type=task` as a filter option that maps to `Note.status.isnot(None)`, (3) enriching `_note_to_item` with task-specific fields, (4) updating counts to include tasks. + +- [ ] **Step 1: Add `"task"` to `_VALID_TYPES` in the route file** + +In `src/fabledassistant/routes/knowledge.py`, change: + +```python +_VALID_TYPES = {"note", "person", "place", "list"} +``` + +to: + +```python +_VALID_TYPES = {"note", "person", "place", "list", "task"} +``` + +- [ ] **Step 2: Update `_note_to_item` to include task fields** + +In `src/fabledassistant/services/knowledge.py`, the `_note_to_item` function builds the item dict. After the existing `elif note.entity_type == "list":` block (which ends around line 48), add a task branch. Find: + +```python + elif note.entity_type == "list": + # Parse markdown task list syntax into structured items + body = note.body or "" + list_items = [] + for line in body.split("\n"): + stripped = line.strip() + if stripped.startswith("- [ ] ") or stripped.startswith("- [x] ") or stripped.startswith("- [X] "): + checked_item = not stripped.startswith("- [ ] ") + list_items.append({"text": stripped[6:], "checked": checked_item}) + item["list_items"] = list_items + item["item_count"] = len(list_items) + item["checked_count"] = sum(1 for i in list_items if i["checked"]) + item["body"] = body + return item +``` + +Replace with: + +```python + elif note.entity_type == "list": + # Parse markdown task list syntax into structured items + body = note.body or "" + list_items = [] + for line in body.split("\n"): + stripped = line.strip() + if stripped.startswith("- [ ] ") or stripped.startswith("- [x] ") or stripped.startswith("- [X] "): + checked_item = not stripped.startswith("- [ ] ") + list_items.append({"text": stripped[6:], "checked": checked_item}) + item["list_items"] = list_items + item["item_count"] = len(list_items) + item["checked_count"] = sum(1 for i in list_items if i["checked"]) + item["body"] = body + + # Task fields — included for all items but only meaningful when is_task + if note.is_task: + item["note_type"] = "task" + item["status"] = note.status + item["priority"] = note.priority + item["due_date"] = note.due_date.isoformat() if note.due_date else None + + return item +``` + +This overrides `note_type` to `"task"` for task items (since `entity_type` returns the `note_type` column which is `"note"` for tasks) and adds status/priority/due_date fields. + +- [ ] **Step 3: Update `query_knowledge` to include tasks** + +In the `query_knowledge` function, the "all types" filter currently excludes tasks. Change the base query and the `else` branch. + +Find: + +```python + base = ( + select(Note) + .where(Note.user_id == user_id) + .where(Note.status.is_(None)) # exclude tasks + ) + + if note_type: + base = base.where(Note.note_type == note_type) + else: + # Exclude tasks — already done above; also exclude any legacy nulls + base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) +``` + +Replace with: + +```python + base = select(Note).where(Note.user_id == user_id) + + if note_type == "task": + base = base.where(Note.status.isnot(None)) + elif note_type: + base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) + else: + # All types including tasks + pass +``` + +- [ ] **Step 4: Update `_semantic_knowledge_search` to include tasks** + +Find: + +```python + candidates = await semantic_search_notes( + user_id=user_id, + query=q, + limit=min(200, limit * 8), + threshold=0.3, + is_task=False, + ) +``` + +Replace with: + +```python + is_task_filter = True if note_type == "task" else (False if note_type else None) + candidates = await semantic_search_notes( + user_id=user_id, + query=q, + limit=min(200, limit * 8), + threshold=0.3, + is_task=is_task_filter, + ) +``` + +Also update the type matching in the filter loop — find: + +```python + for _score, note in candidates: + if note_type and note.entity_type != note_type: + continue +``` + +Replace with: + +```python + for _score, note in candidates: + if note_type == "task" and not note.is_task: + continue + elif note_type and note_type != "task" and note.entity_type != note_type: + continue +``` + +- [ ] **Step 5: Update `query_knowledge_ids` to include tasks** + +Find: + +```python + base = ( + select(Note.id) + .where(Note.user_id == user_id) + .where(Note.status.is_(None)) + ) + if note_type: + base = base.where(Note.note_type == note_type) + else: + base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) +``` + +Replace with: + +```python + base = select(Note.id).where(Note.user_id == user_id) + + if note_type == "task": + base = base.where(Note.status.isnot(None)) + elif note_type: + base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) + else: + pass +``` + +- [ ] **Step 6: Update `get_knowledge_tags` to include task tags** + +Find: + +```python + base = ( + select(func.unnest(Note.tags).label("tag")) + .where(Note.user_id == user_id) + .where(Note.status.is_(None)) + ) + if note_type: + base = base.where(Note.note_type == note_type) + else: + base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) +``` + +Replace with: + +```python + base = ( + select(func.unnest(Note.tags).label("tag")) + .where(Note.user_id == user_id) + ) + if note_type == "task": + base = base.where(Note.status.isnot(None)) + elif note_type: + base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) + else: + pass +``` + +- [ ] **Step 7: Update `get_knowledge_counts` to include tasks** + +Find: + +```python + async with async_session() as session: + stmt = ( + select(Note.note_type, func.count(Note.id)) + .where(Note.user_id == user_id) + .where(Note.status.is_(None)) + .where(Note.note_type.in_(["note", "person", "place", "list"])) + .group_by(Note.note_type) + ) + if tags: + for tag in tags: + stmt = stmt.where(Note.tags.contains([tag])) + rows = list((await session.execute(stmt)).all()) + counts = {row[0]: row[1] for row in rows} + # Ensure all types present even if zero + for t in ("note", "person", "place", "list"): + counts.setdefault(t, 0) + counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list")) + return counts +``` + +Replace with: + +```python + async with async_session() as session: + # Count non-task types + stmt = ( + select(Note.note_type, func.count(Note.id)) + .where(Note.user_id == user_id) + .where(Note.status.is_(None)) + .where(Note.note_type.in_(["note", "person", "place", "list"])) + .group_by(Note.note_type) + ) + if tags: + for tag in tags: + stmt = stmt.where(Note.tags.contains([tag])) + rows = list((await session.execute(stmt)).all()) + counts = {row[0]: row[1] for row in rows} + + # Count tasks separately (is_task = status IS NOT NULL) + task_stmt = ( + select(func.count(Note.id)) + .where(Note.user_id == user_id) + .where(Note.status.isnot(None)) + ) + if tags: + for tag in tags: + task_stmt = task_stmt.where(Note.tags.contains([tag])) + task_count: int = (await session.execute(task_stmt)).scalar_one() + counts["task"] = task_count + + for t in ("note", "person", "place", "list", "task"): + counts.setdefault(t, 0) + counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list", "task")) + return counts +``` + +- [ ] **Step 8: Verify backend changes** + +```bash +cd /path/to/fabledassistant +make typecheck +make test +``` + +Expected: no errors. + +- [ ] **Step 9: Commit** + +```bash +git add src/fabledassistant/services/knowledge.py src/fabledassistant/routes/knowledge.py +git commit -m "feat(knowledge): include tasks in knowledge queries and counts" +``` + +--- + +### Task 2: Frontend — Task card rendering in KnowledgeView + +**Files:** +- Modify: `frontend/src/views/KnowledgeView.vue` + +**Context:** `KnowledgeView.vue` has a `KnowledgeItem` interface and renders cards in a grid. Each card type has type-specific content (person shows relationship/email, list shows checkboxes, etc.). Task cards need status, priority, and due date display. The `activeType` ref controls filtering; it needs `"task"` as a valid value. The type filter sidebar needs a "Tasks" button. The new-note button interaction changes from split-button to toggle. + +- [ ] **Step 1: Add `"task"` to the KnowledgeItem interface and filter type** + +In the ` - - - - diff --git a/frontend/src/views/TasksListView.vue b/frontend/src/views/TasksListView.vue deleted file mode 100644 index 2b8c572..0000000 --- a/frontend/src/views/TasksListView.vue +++ /dev/null @@ -1,678 +0,0 @@ - - - - - From 670de547ad6cdafe327ec92214eca09654e0f2d8 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 8 Apr 2026 10:43:20 -0400 Subject: [PATCH 07/11] docs: add Modern Fable visual identity implementation plan --- ...2026-04-08-visual-identity-modern-fable.md | 785 ++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-08-visual-identity-modern-fable.md diff --git a/docs/superpowers/plans/2026-04-08-visual-identity-modern-fable.md b/docs/superpowers/plans/2026-04-08-visual-identity-modern-fable.md new file mode 100644 index 0000000..365601e --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-visual-identity-modern-fable.md @@ -0,0 +1,785 @@ +# Modern Fable Visual Identity — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the generic indigo dark-mode palette with a distinctive "Modern Fable" visual identity — deep violet + muted gold, signature card types, pill nav, Fraunces-as-narrator typography, and living micro-details. + +**Architecture:** Pure frontend changes across theme CSS, AppHeader, AppLogo, KnowledgeView, ChatPanel, BriefingView, and CalendarView. No backend changes. Each task is independently deployable — palette first, then cards, then nav, then typography, then details. + +**Tech Stack:** Vue 3 SFC (scoped CSS), CSS custom properties, Fraunces font (already loaded). + +--- + +## File Map + +| Action | Path | +|--------|------| +| Modify | `frontend/src/assets/theme.css` | +| Modify | `frontend/src/components/AppLogo.vue` | +| Modify | `frontend/src/components/AppHeader.vue` | +| Modify | `frontend/src/views/KnowledgeView.vue` | +| Modify | `frontend/src/components/ChatPanel.vue` | +| Modify | `frontend/src/views/BriefingView.vue` | +| Modify | `frontend/src/views/CalendarView.vue` | +| Modify | `frontend/src/App.vue` | + +--- + +### Task 1: Color palette update + logo + scrollbar + +**Files:** +- Modify: `frontend/src/assets/theme.css` +- Modify: `frontend/src/components/AppLogo.vue` + +- [ ] **Step 1: Update the dark theme palette in theme.css** + +In `frontend/src/assets/theme.css`, find the `[data-theme="dark"]` block and replace these values: + +```css +[data-theme="dark"] { + --color-bg: #0f0f14; + --color-bg-secondary: #16161f; + --color-bg-card: #1a1a24; + --color-surface: #16161f; + --color-text: #e4e4f0; + --color-text-secondary: #8888a8; + --color-text-muted: #52526a; + --color-border: rgba(124, 58, 237, 0.12); + --color-input-border: rgba(124, 58, 237, 0.22); + --color-primary: #a78bfa; + --color-danger: #f44336; + --color-tag-bg: #2a2a45; + --color-tag-text: #c4b5fd; + --color-shadow: rgba(0, 0, 0, 0.4); + --color-toast-success: #4caf50; + --color-toast-error: #f44336; + --color-status-todo: #9aa0a6; + --color-status-todo-bg: #2a2a35; + --color-status-in-progress: #a78bfa; + --color-status-in-progress-bg: #2a2a45; + --color-status-done: #4caf50; + --color-status-done-bg: #1b3a20; + --color-priority-low: #80cbc4; + --color-priority-low-bg: #1a3a38; + --color-priority-medium: #fdd835; + --color-priority-medium-bg: #3a3520; + --color-priority-high: #f44336; + --color-priority-high-bg: #3a1a1a; + --color-wikilink: #c4b5fd; + --color-wikilink-bg: #2a1a45; + --color-overdue: #f44336; + --color-code-bg: #12121a; + --color-code-inline-bg: #1a1a2a; + --color-table-stripe: #14141e; + --color-success: #4ade80; + --color-warning: #facc15; + --color-input-bar-bg: #1a1a24; + --color-input-bar-text: #e4e4f0; + --color-input-bar-placeholder: rgba(228, 228, 240, 0.35); + --color-overlay: rgba(0, 0, 0, 0.65); + --color-bubble-user-bg: rgba(255, 255, 255, 0.04); + --color-bubble-user-border: rgba(255, 255, 255, 0.10); + --color-bubble-user-text: #b0b0c8; + --color-bubble-asst-shadow: 0 4px 28px rgba(124, 58, 237, 0.14), 0 2px 8px rgba(0, 0, 0, 0.4); + --color-accent-warm: #d4a017; + --color-accent-warm-light: #e8c45a; + --color-primary-solid: #7c3aed; + --color-primary-deep: #5b21b6; +} +``` + +Note: `--color-accent-warm`, `--color-accent-warm-light`, `--color-primary-solid`, and `--color-primary-deep` are new variables. + +- [ ] **Step 2: Update the light theme palette** + +In the `:root` block, update these values: + +```css +:root { + --color-bg: #f5f5fb; + --color-bg-secondary: #ededf5; + --color-bg-card: #ffffff; + --color-surface: #f0f0f8; + --color-text: #1a1a1a; + --color-text-secondary: #666666; + --color-text-muted: #999999; + --color-border: #dddde8; + --color-input-border: #c8c8d8; + --color-primary: #7c3aed; + --color-danger: #d93025; + --color-tag-bg: #ede5ff; + --color-tag-text: #6d28d9; + --color-shadow: rgba(0, 0, 0, 0.08); + --color-toast-success: #34a853; + --color-toast-error: #d93025; + --color-status-todo: #5f6368; + --color-status-todo-bg: #e8eaed; + --color-status-in-progress: #7c3aed; + --color-status-in-progress-bg: #ede5ff; + --color-status-done: #34a853; + --color-status-done-bg: #e6f4ea; + --color-priority-low: #5f9ea0; + --color-priority-low-bg: #e0f2f1; + --color-priority-medium: #f9a825; + --color-priority-medium-bg: #fff8e1; + --color-priority-high: #d93025; + --color-priority-high-bg: #fce8e6; + --color-wikilink: #7b1fa2; + --color-wikilink-bg: #f3e5f5; + --color-overdue: #d93025; + --color-code-bg: #f0f0f8; + --color-code-inline-bg: #eaeaf4; + --color-table-stripe: #f4f4fb; + --color-success: #22c55e; + --color-warning: #eab308; + --color-input-bar-bg: #eaeaf3; + --color-input-bar-text: #1a1a1a; + --color-input-bar-placeholder: rgba(0, 0, 0, 0.4); + --color-overlay: rgba(0, 0, 0, 0.45); + --color-bubble-user-bg: rgba(0, 0, 0, 0.04); + --color-bubble-user-border: rgba(0, 0, 0, 0.10); + --color-bubble-user-text: #3a3a4a; + --color-bubble-asst-shadow: 0 2px 16px rgba(124, 58, 237, 0.10), 0 1px 4px rgba(0, 0, 0, 0.06); + --radius-sm: 6px; + --radius-md: 12px; + --radius-lg: 18px; + --radius-pill: 9999px; + --focus-ring: 0 0 0 2px rgba(124, 58, 237, 0.4); + /* Layout */ + --page-max-width: 1200px; + --page-padding-x: 1rem; + --sidebar-width: 260px; + /* New brand variables */ + --color-accent-warm: #b8860b; + --color-accent-warm-light: #d4a017; + --color-primary-solid: #7c3aed; + --color-primary-deep: #5b21b6; +} +``` + +- [ ] **Step 3: Update scrollbar color** + +Find: +```css +::-webkit-scrollbar-thumb { + background: rgba(99, 102, 241, 0.25); +} +::-webkit-scrollbar-thumb:hover { + background: rgba(99, 102, 241, 0.45); +} +``` + +Replace with: +```css +::-webkit-scrollbar-thumb { + background: rgba(124, 58, 237, 0.25); +} +::-webkit-scrollbar-thumb:hover { + background: rgba(124, 58, 237, 0.45); +} +``` + +- [ ] **Step 4: Update focus ring** + +Find: +```css + --focus-ring: 0 0 0 2px color-mix(in srgb, var(--color-primary) 40%, transparent); +``` + +Replace with: +```css + --focus-ring: 0 0 0 2px rgba(124, 58, 237, 0.4); +``` + +- [ ] **Step 5: Update AppLogo gradient** + +In `frontend/src/components/AppLogo.vue`, the logo uses `var(--color-primary)` which will automatically pick up the new violet value. No code change needed — the CSS variable update handles it. + +However, add a gradient `` for the book fill to use the deep gradient instead of a flat color. Find the `