a01deeb851db9daba1dccbcc5434c9c84e91632d
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
62f3a485ad |
fix(usage): one seam attaches the surfaced-vs-opened chip, and the Knowledge browse uses it (#4230)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 52s
CI & Build / Python tests (push) Failing after 1m2s
CI & Build / Build & push image (push) Skipped
`usage_for_notes` is named for notes and works on every note row, yet the chip reached snippets and rules only. Notes had it nowhere. Lessons had it collected and shown nowhere a person could reach, because #4196 taught `/api/lessons` to attach it and `KnowledgeView` — the only lesson list in the UI — browses through `/api/knowledge`, so `listLessons` still has no consumer. The cause was not a missing line. SEVEN call sites carried their own copy of the same few lines: two REST lists, two REST details, two MCP lists, one MCP detail. Each read perfectly well alone, so "which doors attach usage?" had no answer anywhere in the code — the same asymmetry test_system_tagging_door_parity.py records for System tagging (#4249), where whichever door nobody exercised for a kind is the one that never grew the feature. `attach_usage(rows, key="id")` is now that answer, and all seven go through it. A detail payload is a one-row list, so the single-record doors share the seam rather than keeping a second shape beside it. Deliberately NO try/except: the fail-open already lives in `usage_for_notes`, which reports through `_report_failure("readout")` and returns the zero-filled map. Wrapping it again would swallow the REPORT as well as the error, and a silently-swallowed readout failure is exactly #2663 — every counter reading zero in production for weeks while the writes landed fine. `/api/knowledge` now attaches usage, which closes both holes at once: it is how notes, lessons and processes are all browsed. `KnowledgeView` renders the badge on the card footer, looking the advice up per row because the feed is mixed. The advice moves to utils/deadWeight.ts. Canon #3460 says each caller owns its own const, and that held while each caller showed ONE kind; a mixed feed would need five of its own and the next surface another five. The canon's actual invariant — advice is kind-specific and never baked into the badge — is kept: it is still a prop. The three existing callers now read the same table, so the sentence has one home rather than four. Recorded against #3460 so the next reader is not left re-litigating it. `_row_id` rejects bools explicitly: `int(True)` is 1, so a row carrying a flag under the key would be credited with note #1's counts, and a wrong chip is worse than no chip because it reads as a measurement. A row with no usable id is skipped rather than failing the page. Tests pin the PROPERTY, not one route: no door calls the aggregate directly (AST, so a comment naming it is not a false positive), and every door that shows usage reaches the seam. Plus the N+1 guard — one aggregate per page, asserted on await_count, because the per-row version reads more naturally and is invisible in review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
a8b2040216 |
feat(rules): the edit history is visible in the slide-over (#3243, milestone 323 step 4)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / integration (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m20s
CI & Build / Build & push image (push) Successful in 40s
Rule 27: a history nobody can read is not shipped. `RuleHistoryPanel.vue` sits below the fields in `RuleEditorSlideOver`, where a rule is read in full — not on the list row, where a history entry point would compete with the row's job. REUSE, DECIDED FIELD BY FIELD RATHER THAN ALL AT ONCE. DiffView.vue is reused unchanged: it takes DiffLine[] and nothing note-shaped. HistoryPanel.vue is NOT, and its props are the reason — noteId + currentBody, a NoteVersion carrying tags and pin columns, a fetch of /api/notes/…, a restore emit, pin/unpin buttons. Rules have no tags, no pins, and deliberately no restore, and a rule's text is EIGHT fields rather than one body, which changes the reader's question from "what changed" to "which fields moved". Recorded here rather than forked silently, per #3207. THE FORK THAT WAS ALREADY THERE. The LCS walk existed three times — privately in useAssist.ts, and again inside HistoryPanel.vue and VersionHistorySection.vue — character-identical apart from quote style, because computeDiff was never exported. Rather than add a fourth copy, it moves to utils/diff.ts and the three become imports; the extraction was verified equivalent to all three before anything was deleted. DiffLine is re-exported from useAssist so its existing importers are untouched. WHAT A ROW SHOWS: when, and which fields moved. A version holds the text the edit REPLACED, so the edit is the step from a row to the next NEWER state — the row above it, or, for the newest row, the rule as it stands now. Comparing against the row below would attribute every change to the wrong edit. A field nobody has fetched yet reads as neither changed nor unchanged. An edit that touched verify_with is badged "check reset", because that edit silently cleared verified_at (milestone 312) and put the rule back at the top of the staleness sweep — a moment visible nowhere else. The badge is a 12% color-mix TINT, not solid `--fs-warning`. `--fs-warning-fg` is defined in theme.css as "warning TEXT on a warning tint", so painting it over the solid token is exactly the same-hue contrast failure #3141 records. Every var() the component references resolves against theme.css, checked before pushing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2a6c55dacb |
refactor(frontend): auth-shared.css, apiErrorMessage, one date helper per shape, modal canon in components.css — the frontend pass of the shape audit (#2831 #2832, milestone 296)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 22s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 16s
- assets/auth-shared.css: the five auth views carried byte-identical scoped
copies of the page/card/brand/footer/field/input/error rules (~60 lines
each); they now load one stylesheet the way the editors load
editor-shared.css. .closed-msg/.error-block/.success-msg (identical bodies)
are one .auth-note; the form rules are scoped under .auth-card so nothing
leaks into the rest of the app.
- api/client.apiErrorMessage(e, fallback): the one place the {"error"} envelope
is unpacked; replaces ten six-line `"body" in e` catch blocks.
- utils/dateFormat: fmtDate / fmtStamp / fmtLogStamp replace eight local
formatDate/formatTime copies (three byte-identical pairs); the file’s old
Calendar/Home helpers had no callers and are gone. useRelativeTime gains
relativeTimeOrDate for the two workspace panels’ identical variant.
- components.css now owns the .modal-* shape (overlay/card/title/message/
actions/btn/primary/danger). It was copied into four views and lived in
editor-shared.css, which ConfirmDialog — styleless, teleported to <body> —
silently depended on: opened from SnippetDetailView before any editor view
had loaded, it rendered unstyled. Views keep only their own overrides.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4ba544e2af |
refactor(theme): retire the --color-* shim — the sweep it promised, run
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 37s
#2533. theme.css claimed "removing this block is a rename sweep across the
components, tracked separately" — written in
|
||
|
|
dcd4efcea0 |
refactor(design): retire /design — a surface that could only inspect itself
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 59s
CI & Build / Build & push image (push) Successful in 40s
The design surface is for the projects an install tracks. /design read the running app's own stylesheet — names out of a bundled theme.css, values out of getComputedStyle(document.documentElement) — so it could only ever describe the instance serving the page. Scribe is one project among the projects Scribe tracks; it gets no view hardcoded into every install. The mechanism that makes this a tool rather than a mirror already existed and already covers Scribe: scripts/check_design_tokens.py runs in CI against a sheet path it knows nothing about, using check_code_against_tokens — the same engine behind check_snippets_against_system. /design was redundant even here. Removed: DesignView, DesignTabs (nothing left to tab between), api/design.ts, routes/design.py and its blueprint, the /design route, ui_design_system() and its setting, and the Settings picker that designated "this app's UI". utils/designTokens.ts and utils/designDrift.ts go with it — between them they were the browser-reading half. What survives is utils/designValues.ts, which works on a record rather than a document: valueForMode, modesPresent, and resolveDeclared. resolveDeclared gained real isolation in the move. Custom properties inherit and `all: initial` does not reset them, so a probe sitting in this page would resolve any reference a record leaves undeclared against the SURROUNDING app's tokens — previewing another project's system would quietly borrow this one's palette wherever that system was incomplete, and a token already reported under unknown_refs would render as though it were fine. Undeclared references are now blanked on the probe first, so they resolve to nothing, which is what the record says they are. Migration 0075 absorbs ui_design_system_id alongside design_rulebook_id rather than an 0076 undoing it: 0075 has not run anywhere, since dev is unmerged and deploys come from main. Both keys named a design source for the running install, and a project already carries its own pointer. This retires the agreement panel shipped yesterday. It asked whether the sheet was actually loaded and applied — the one question a record cannot answer about itself — but only ever about the app you are already inside. Nothing replaces it; recorded in #2430 rather than quietly dropped. Step 1 of milestone #274. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
5b824c1626 |
feat(design): the panel now asks whether the app agrees with its own sheet
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 58s
CI & Build / Build & push image (push) Successful in 43s
Retiring rulebook #2 left the /design drift panel with no data source, and because its empty state was well-written the feature read as working while it could only ever render "nothing designated" (#2419). The original question is genuinely gone: theme.css is generated from design system 2, so checking the system against a sheet derived from it would be a tautology. The question that survives is the one no server can answer. A generated sheet still has to be LOADED and APPLIED, and nothing checked that it was: absent the record declares a token the app doesn't have — the sheet was never regenerated after the record changed, or never loaded differs the app has it with another value — a stale sheet, or a later rule that overrode it unrecorded the app declares a token in the record's own family that the record has never heard of Both sides go through the same engine so the comparison is honest: declared values are set on an offscreen probe and read back, which performs the same var() substitution the browser already did to the live values. Comparing raw strings would mark every derived token as drift. The designation moved with the feature — design_rulebook_id becomes ui_design_system_id, with a migration deleting the retired key rather than leaving an inert row. The prose extractor it fed goes too (#2288 said its runtime role ended when the import landed). Three orphans of the same shape, found alongside and fixed here: - darkOverriddenNames hardcoded [data-theme="dark"]. The sheet went dark-first months ago, so it matched nothing and the "mode-aware" flag silently left the gallery. Now matches the SHAPE of a mode selector, which also holds for an install whose modes aren't light and dark. - groupFor's prefix table never heard of --fs-, so 110 tokens sat under "other". Groups now come from the record where there is one; the table can only know families that shipped with the product (rule #115). - The type scale was a hand-written table of nine sizes marked "no token", true when written and false since the scale was recorded. Now rendered from whatever size tokens the sheet declares, so it can't go stale twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
4ca3ab02c4 |
feat(design-explorer): the drift panel — rulebook says X, tokens say Y
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 15s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 40s
Milestone #251 step 5 (#2262), plus the Settings control that makes it reachable. The comparison is deliberately thin — set arithmetic over live token values, which is the one thing the browser knows and the server doesn't. The hard half (prose to claims) already lives in Python where pytest can assert on it. Three claim kinds, and the third inverts the test: a `token` claim asks whether a custom property of that name exists; a `color` claim asks whether any token resolves to that value; a `prohibited_color` claim FAILS when present. normalizeColour is the client-side twin of normalize_hex and has one job the server cannot do: getComputedStyle reports colours as rgb()/rgba() regardless of how they were authored. So one colour has three spellings in play — #FFFFFF in the rulebook, #fff in the stylesheet, rgb(255,255,255) from the browser — and a comparison that misses any of them under-reports silently rather than erroring. THE PANEL STATES ITS OWN BLIND SPOT, which matters more than it sounds. This compares the rulebook against TOKENS. A literal hardcoded in a component, where a token should have been referenced, is invisible to it — the drift isn't in the tokens at all (#2275: 67 hardcoded whites against a rule forbidding pure white). Reading those would mean bundling every SFC's source into the app; the check belongs in CI and is tracked at #2277. A drift report that silently omitted a whole category would invite the reader to conclude the category is clean, so the panel says so in the panel rather than in a comment nobody reads. Findings are ranked violated → missing → ok, and `ok` rows are hidden behind a toggle. Same principle the auto-inject menu is built on: a short list that gets read beats a complete one that doesn't. Settings gains a rulebook picker. "None" is a first-class choice, not an unset error — most installs have no rulebook describing their design system, and saving empty DELETES the setting rather than storing a zero. The panel's empty state points at Settings and Settings points back at the panel, so neither is a dead end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
61e6e38419 |
feat(design-explorer): token inventory — what exists and what it resolves to
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 31s
Milestone #251 step 1 (#2258). Foundation for the gallery and the drift panel. Parses NAMES from theme.css and asks the BROWSER for every value. That split is deliberate. Extracting `--foo` is a trivial regex; extracting its value is not — theme.css has nested parens, commas inside rgba(), var() chains, multi-part shadows and gradients. getComputedStyle already resolves all of it, reports what actually won the cascade, and — the reason that matters here — reflects live overrides set on a container, which is exactly what the preview surface needs (#2261). Parsing values would report what the file says rather than what the user is looking at. It also keeps the error-prone half out of our code, which matters because the frontend has no test runner: `vue-tsc --noEmit` is the entire check. Logic that can't be unit-tested should be logic that can't be very wrong. readTokens(host) takes an element, so the same function reads app-wide values from :root and scoped values from inside a preview container. A BUG CAUGHT BEFORE SHIPPING, worth recording because the first version looked obviously right: the declaration regex originally required the match to follow `{` or `;`, to avoid matching var() uses. That silently dropped every declaration preceded by a COMMENT — including --color-bg, the first and most-used token in the file. 67 of 70 tokens found, no error, no warning. The anchor was never needed. A declaration is `--name:` and a reference is `var(--name)` or `var(--name,` — the colon alone discriminates. Comments are stripped first so commented-out declarations aren't counted. Verified against the real stylesheet: 70 unique tokens, 60 dark-overridden, 10 light-only, every group resolving, zero var()-only false positives. Also records a constraint discovered while building, which shapes step 6: light is declared on :root and dark on [data-theme="dark"], so an attribute selector can ADD dark to a subtree but nothing can add light back. Dark-inside-light previews work; light-inside-dark previews cannot, until a [data-theme="light"] block exists. readTokensForMode documents this rather than pretending otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
749a60b9fd |
feat: interactive checkboxes in list note viewer
- renderMarkdown() accepts interactiveCheckboxes option: removes disabled="" and stamps data-task-index on each checkbox in the marked HTML output - NoteViewerView detects list notes by body content (- [ ] / - [x] pattern) and passes interactiveCheckboxes: true when rendering - onBodyChange() handles checkbox change events: toggles the matching line in the body, optimistically updates the store, then PATCHes the note - prose.css adds .prose--checklist rules for marked output: no bullet, flex row, accent-color, line-through on checked items via :has() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
145c18d8a3 |
refactor: DRY calendar additions + define --color-surface in theme
- Define --color-surface in theme.css (light: #f0f0f8, dark: #1a1b22) — was used across 10+ components with no-op fallbacks; now properly defined alongside --color-bg and --color-bg-card - Extract shared date formatters into utils/dateFormat.ts: fmtTime, fmtDateTime, fmtRelativeDateTime, fmtDayLabel, fmtCompact - Replace duplicate inline formatters in CalendarView, HomeView (formatUpcomingTime), and KnowledgeView (formatEventDate) - CalendarView: replace hardcoded rgba(99,102,241,0.4) hover colour with color-mix(in srgb, var(--color-primary) 40%, transparent); fix --color-input-bg fallback to use var(--color-bg); remove hardcoded hex fallbacks now that --color-surface is defined Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
00643c778e |
security: fix 10 vulnerabilities from security audit
- SSRF: block private/internal URLs in image cache fetch - SSRF: block private/internal URLs in RSS feed fetch (scheme guard) - SSRF: block private/internal URLs in CalDAV URL setting - Auth: require login for GET /api/images/<id> (was unauthenticated) - Auth: restrict Ollama model pull/delete to admin users only - Info disclosure: remove email from /api/users/search response - OAuth: skip email-based account linking when email_verified is false - Config: raise hard error on default SECRET_KEY when SECURE_COOKIES=true - Rate limit: document proxy header requirement; add startup warning - XSS: remove src/alt from global DOMPurify ADD_ATTR allowlist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
699e525cb9 |
refactor: DRY pass on frontend — shared palette, composable, and tab loader
- Extract milestoneColor to utils/palette.ts; remove duplicate in HomeView + ProjectListView - Create useBackgroundRefresh composable; wire into HomeView + BriefingView (removes manual setInterval/clearInterval boilerplate) - Extract _loadTabContent() in SettingsView so watch and onMounted share one tab→loader mapping - Move raw fetch() api-key calls to typed helpers in api/client.ts (listApiKeys, createApiKey, revokeApiKey) - Drop unused onUnmounted import from BriefingView Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
aab478359b |
fix: decode HTML entities in wikilinks before re-escaping
marked encodes " to " in text nodes, causing linkifyWikilinks to double-escape it (& → &) so the visible link text showed " instead of the actual character. Decode marked's entities on the matched title/label before running escapeHtmlAttr so the output is correct in both the href attribute and the visible link text. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c65aad6639 |
Upgrade all major frontend dependencies
- TipTap 2 → 3: Extension from @tiptap/core, Placeholder from @tiptap/extensions, TaskList/TaskItem from @tiptap/extension-list, link: false in StarterKit (now bundles Link), @tiptap/core added - marked 15 → 17: heading renderer updated to tokens/parseInline API - Pinia 2 → 3, Vue Router 4 → 5 (no code changes required) - Vite 6 → 7, @vitejs/plugin-vue 5 → 6, vue-tsc 2 → 3 - TypeScript 5.6 → 5.9: fixed Uint8Array<ArrayBuffer> strictness in push.ts, removed unused bodyEl ref in NoteViewerView.vue - .npmrc: legacy-peer-deps=true for TipTap v3 peer dep resolution TipTap 3 new capabilities now available: static renderer (createStaticRenderer), MarkViews, @tiptap/extensions package. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ef141f07f8 |
UI polish pass: word count, slash commands, task lists, graph peek, bulk delete, export
- WordCount component (toggle words/chars vs read time, persisted mode) - TipTap: TaskList/TaskItem extensions, slash command menu (H1-H3, lists, code, quote, task) - Markdown serializer: task list → `- [ ]` / `- [x]` roundtrip - GraphView: slide-in peek panel for note/task nodes (body, tags, linked nodes); tag nodes still navigate - ChatView: bulk-select conversations with two-click confirm delete + chat retention policy (default 90d) - NoteEditorView: pill tab bar with animated edit/preview toggle + WordCount in toolbar - WorkspaceNoteEditor: inline search with tag matching, inline new-note creation, WordCount - WorkspaceTaskPanel: task body rendered in slide-over + Edit link - Settings: data export (Markdown ZIP / JSON) via GET /api/export - Accessibility: skip-to-content link, aria-labels on all icon-only buttons - Fix: export route used non-existent fabledassistant.database — corrected to async_session() - Fix: ToolCallRecord.status type now includes "running" (was causing TS build error) - Dockerfile: upgrade npm to latest before install to suppress major-version notice - npm audit fix: patched minimatch (ReDoS) and rollup (path traversal) — 0 vulnerabilities Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1890f0c7f1 |
Fix image search: embed markdown instead of describing URLs
- tools.py: search_images result now includes 'embed' (ready-to-use markdown image syntax) and 'citation' fields instead of raw 'local_url'; adds 'instructions' field so the model knows to render them verbatim - llm.py: system prompt now explicitly tells the model to embed images using the 'embed' field rather than describing or listing URLs - markdown.ts: explicitly allow src/alt in PURIFY_OPTS_FULL so img tags are never stripped by DOMPurify Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
7947134e22 |
Fix tag handling for multi-word tags
Tags with spaces (e.g. #science fiction) were breaking extraction because TAG_RE only matched word characters — it would stop at the space and extract #science instead of #science-fiction. - TAG_RE (backend + frontend): add hyphens to character class so #science-fiction is recognized as a single tag: [\w][\w-]* per segment - System prompt: instruct LLM to use hyphens in multi-word tags, never spaces - tag_suggestions.py: update prompt example + sanitize output by replacing spaces with hyphens as a safety net regardless of LLM output - append-tag route: sanitize incoming tag (spaces → hyphens) before appending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
667ccd70cd |
Security audit, bug fixes, auto-save, and writing assistant improvements
Backend security & correctness: - Add rate_limit.py: sliding-window rate limiter (asyncio) applied to login, register, forgot/reset password endpoints (10/60s or 5/300s per IP) - app.py: add security headers in after_request (X-Frame-Options, CSP, X-Content-Type-Options, Referrer-Policy) using setdefault to preserve SSE headers - auth.py: refactor duplicate login_required/admin_required into shared _check_auth() - config.py: add TRUST_PROXY_HEADERS for proxy-aware client IP resolution - routes/auth.py: rate limiting, _client_ip() helper, cleaned-up reset_password route - routes/chat.py, notes.py, tasks.py: int() DoS fix on last_event_id; limit capped at 500; date.fromisoformat() wrapped in try/except → 400 on invalid dates - services/auth.py: fix Setting.user_id update bug (filter on NULL not user.id); reset_password_with_token returns int|None (user_id) instead of bool - services/backup.py: add _security_notice to full backup JSON export - services/assist.py: system prompt explicitly preserves markdown list structure and nested indented sub-items Infrastructure: - docker-compose.yml: add healthcheck on app service (/api/health, 10s interval) - .dockerignore: prevent secrets/node_modules/__pycache__/.env.* leaking into build Frontend bug fixes: - TaskCard.vue, TaskViewerView.vue: fix isOverdue() timezone bug (ISO string compare) - useAssist.ts: accept() now resets state to idle when document changed since proposal - stores/chat.ts: fix memory leak in _pollUntilLoaded() (try/catch around fetchStatus) - TiptapEditor.vue: selection offset uses closest-match strategy (not first-match) - utils/markdown.ts: explicit DOMPurify config with FORCE_BODY; remove as const (DOMPurify expects mutable string[]) New features: - Auto-save (5-minute interval) in NoteEditorView and TaskEditorView — only when editing an existing dirty record; silent on error, shows "Auto-saved" toast - sectionParser.ts: top-level bullet/numbered list items are now individual sections in the AI Assist panel (previously treated as one undifferentiated block) - editor-shared.css: extracted ~500 lines of CSS duplicated between both editors; includes .inline-assist-btn at global scope (required for teleported elements) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
0c4e7fe5cb |
Redesign writing assistant UI: right-side panel, diff view, proofread, floating inline assist
- sectionParser.ts: add parseFallbackSections() — splits heading-less notes by paragraph boundaries; single-line ≤120 char paragraphs become pseudo-headings so Q&A-style notes get individual selectable sections in the assist panel - useAssist.ts: add DiffLine type + computeDiff() (LCS line diff), isProofreading ref, diff computed, proofread() method (full-document one-click), reset isProofreading in accept() and reject() - NoteEditorView + TaskEditorView: complete layout redesign - Assist panel moves from bottom 33% to right-side 320px column (flex-row) - ✨ Assist toggle button in toolbar, state persisted to localStorage - Floating ✨ pill button (teleported to <body>) above text selections; click opens panel with selection pre-loaded and instruction textarea focused - Proofread button in panel header: sends entire document, labels streaming as "Proofreading document..." and review as "Document proofread" - Review state shows LCS line-level diff (red removed, green added); "Show full text" toggle switches to rendered proposal - Mobile (≤768px): panel drops to bottom 45% height - theme.css: add global .inline-assist-btn styles for teleported floating button - Site-wide max-width increase: list/chat/settings views 960px→1200px; viewer layout 1200px→1400px (content area 960px→1100px); editor pages already at 1400px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e02b681e91 |
Add invitation system, table of contents, actionable dashboard, and settings improvements
Invitation system (Phase 5.7): - invitation_tokens table (migration 0012) with SHA256-hashed tokens, 7-day expiry - Admin CRUD endpoints: POST/GET/DELETE /api/admin/invitations - Branded invitation email with registration link - /register-invite frontend view with token validation and account creation - Admin UI: invite form + pending invitations table with revoke - Configurable base URL setting for email links (replaces hardcoded Config.BASE_URL) Table of contents + markdown improvements (Phase 5.8): - TableOfContents component: sticky sidebar, heading parsing, smooth-scroll - Custom marked renderer adds id attributes to headings for anchor links - stripFirstLineTags() prevents leading #tags from rendering as headings - NoteViewerView/TaskViewerView flex layout with TOC sidebar (hidden ≤1200px) - Model catalog refresh: added llama3.2/3.3, gemma3, qwen3, phi4, deepseek-r1, qwen2.5-coder, dolphin3; added Reasoning category; removed discontinued models - Settings model section split into Installed/Available tabs Actionable dashboard (Phase 5.9): - due_before/due_after query params on /api/tasks (exclusive < / inclusive >=) - HomeView rewritten: overdue (red accent), due today, in progress, chats, notes - 5 parallel API calls via Promise.allSettled - Client-side done filtering and in-progress deduplication - Task sections hidden when empty; status toggle removes done tasks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
586026bff6 |
Add Tiptap inline-preview editor, layout improvements, and README
Replace plain textarea with Tiptap (ProseMirror) WYSIWYG editor for notes and tasks. Headings, bold, lists, and code blocks now render inline while editing. Tags and wikilinks get visual highlighting via decoration plugins, and autocomplete uses @tiptap/suggestion with heading disambiguation. Layout: pin navbar with app-shell flex column (100dvh), sticky editor toolbar above scrolling content, AI Assist panel fixed at bottom 1/3 with full-height section selector and prompt. Increase max-width to 960px across all views. Hide assist controls during streaming/review. Other fixes: markdown paste handling, auto-syncing assist sections via body watcher, trailing newline on AI accept, ChatView height fix. Add README.md describing the app, usage guide, and technical overview. Update summary.md with Phase 5.2 roadmap and current status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
de899ebc50 |
Fix apostrophe rendering: prevent HTML entities from matching as tags
The TAG_RE regex in linkifyTags() was matching #39 inside ' as a tag, breaking the HTML entity and preventing apostrophe rendering. Added (?<!&) negative lookbehind so # preceded by & is skipped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
38b1ac933e |
Add settings page, model management, and chat UX improvements
- Settings infrastructure: key-value settings table, GET/PUT API, Pinia store - Configurable assistant name (default "Fable") in settings and LLM system prompt - Model catalog with 18 models in 3 categories (General Purpose, Coding, Uncensored / Creative Writing) with download/select/remove functionality - Move Ollama status indicator from chat views to global nav bar - Chat bubble layout: user messages right-aligned, assistant left-aligned - Floating dark input bar with auto-focus and circular send button - Fix HTML entity rendering (' apostrophe issue in marked/DOMPurify pipeline) - Fix new chat button navigation (fetchConversation before router.push) - Recent chats section on home page with "New Chat" button - Update summary.md with Phase 4.5 changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
22a3a3c1d1 |
Initial commit: note-taking/task-tracking app with LLM integration scaffold
Vue 3 + TypeScript frontend with Pinia stores, markdown rendering (marked + DOMPurify), wikilink/tag linkification, and autocomplete. Quart async backend with SQLAlchemy 2.0, PostgreSQL ARRAY columns, task-note companion linking, backlinks, and note-to-task conversion. Docker Compose setup with PostgreSQL 16 and Ollama. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |