84640a0dc4a96371cce20ac8968bf47623d91fb1
723 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
84640a0dc4 |
fix(events-tools): refuse ambiguous query, require event_id to disambiguate (#161)
Root cause of the 2026-04-29 dentist-appointment incident: the model
called update_event(query="Appointment") when two events had
"Appointment" in their titles. find_events_by_query returned both,
upcoming-first ordered by start_dt — matches[0] was id=2 (a stale
pre-existing event with garbage end_dt), not id=15 (the one the user
just created via the journal flow). update_event_tool silently took
matches[0] and mutated the wrong event.
Fix: a new resolver helper `_resolve_event_for_action` funnels both
update_event_tool and delete_event_tool through one disambiguation
path. Lookup precedence:
- `event_id` → exact get_event lookup, no query at all
- `query` matching exactly one event → proceed
- `query` matching zero → return success=False, "no event found"
- `query` matching 2+ events → return success=False with a
`candidates` array of {id, title, start_dt, location} so the
model can pick one and call again with `event_id`
The candidates list is capped at 8 to keep the model's context tight.
The error message names the count and the next-step ("pass event_id
or refine the query") so the model can self-correct in one turn.
For delete_event, the disambiguation is even more important — the
silent-matches[0] path would have deleted the wrong event outright
rather than just mutating it. The tool description leans into that:
"Deleting the wrong event is a costly user error; never guess."
Tool surface change: `query` and `event_id` are now both optional;
the tool errors clearly when neither is supplied. The model already
knows id values from prior tool results (returned in `data.id`),
which is the natural feeder for the disambiguation flow.
5 new tests in test_calendar_tool_tz.py cover:
- ambiguous query → success=False with candidate list, no mutation
- event_id supplied → bypasses query lookup entirely
- non-existent event_id → clear "no event found" error
- neither identifier → "query or event_id required" error
- same disambiguation enforced for delete_event_tool
46 calendar/events tests pass; ruff clean.
Closes Fable #161.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4c58603009 |
feat(events): replace end_dt column with duration_minutes (#160)
Structural fix for the "end before start" bug class observed on prod
2026-04-29. Bad data became inexpressible at the schema level instead
of getting trapped in defensive read-path filters.
The hotfix that landed earlier today (
|
||
|
|
b7e7073425 |
Revert "fix(events): tolerate corrupt end_dt + reject end<=start at write time"
This reverts commit
|
||
|
|
94b169f31c |
fix(events): tolerate corrupt end_dt + reject end<=start at write time
A prod event surfaced today with `start_dt=2026-05-01T12:00Z` and `end_dt=2026-03-30T12:00Z` — end was 32 days BEFORE start, almost certainly from an earlier tool-call mishap (Fable #161). The list_events filter trusted the bogus end_dt and excluded the event from every read path that hit the upcoming window, even though start_dt was correctly in range. The event stayed visible in the calendar grid (different range) but vanished from "Upcoming", search, briefings, and journal prep events list. This is the hotfix half of the response. The structural follow-up is Fable #160 — replace end_dt with a duration column so invalid state becomes inexpressible. ## A. Filter robustness in list_events Treat `end_dt <= start_dt` as if no end_dt exists. The filter now splits into two branches: - valid duration: end_dt IS NOT NULL AND end_dt > start_dt AND end_dt >= date_from - no/invalid duration: (end_dt IS NULL OR end_dt <= start_dt) AND start_dt >= date_from Same change applied to the recurring-event expansion's `duration` calculation, which was producing negative timedeltas for corrupted rows and computing nonsensical occurrence end times. ## B. Write-side validation in create/update `create_event` and `update_event` now raise ValueError when the resulting state would have end_dt <= start_dt. Update validates against the *post-update* state, not just the field being changed — so pushing start_dt past an existing end_dt also fails loudly. Bad data shouldn't be persistable from any write path. Surfaced cleanly: - Calendar tool wrappers (create_event_tool / update_event_tool) catch ValueError and return `{success: false, error: ...}`, which the model can read and self-correct. - Route handlers (POST /api/events, PATCH /api/events/<id>) catch and return HTTP 400 with the validator's message instead of letting it bubble to a 500. 4 new tests in test_events_service.py: - create rejects end before start - create rejects equal start/end (zero duration) - update validates the post-update state (start pushed past existing end) - list_events surfaces an event whose end_dt is before its start_dt 34 event-related tests pass; ruff clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2db23cec7a |
refactor(events): turn EventSlideOver into a centered modal with auto-save
User feedback: the right-edge slide-over panel pinned its action buttons
to a thin floor band where the destructive ghost-style Delete button was
functionally invisible against the dark surface. Save / Cancel / Delete
all sat in the same floor strip, isolated from the form content.
This refactor changes the surface and the commit model.
## Centered modal, not slide-over
Backdrop dim covers the whole viewport; the panel sits centered with a
12px corner radius and a soft shadow. The form scrolls internally when
content overflows the viewport (max-height: calc(100vh - 2.5rem)).
File kept as `EventSlideOver.vue` to avoid touching the three consumers
(CalendarView, HomeView, ToolCallCard).
## Action buttons removed; close = save
- Save button: gone. Auto-save fires when the user closes via X, Esc,
backdrop click, or pressing Enter inside a text field.
- Cancel button: gone. Esc / X / backdrop click already cover dismiss;
a labeled "Cancel" was redundant.
- Delete button: moved to the header as a Trash2 icon (edit mode only).
Click → header swaps to inline confirm "Delete this event? [Yes,
delete] [No]" — same two-step flow, just relocated. Esc during the
confirm cancels back to edit mode rather than closing the modal,
giving the user a clear way out of the destructive prompt.
## Validity-aware close
All exit paths funnel through `attemptClose`:
- Form valid → save (POST or PATCH), then close.
- Form invalid in EDIT mode → discard the in-memory change and
close, with a toast naming the missing field
("Title required — change discarded"). Keeps the user from
silently corrupting an event.
- Form invalid in CREATE mode → close silently. Nothing was
committed; calling that out adds noise.
Emit signature unchanged (close / created / updated / deleted), so the
three consumers continue to work without edits.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
35fab6cbf7 |
fix(calendar-tool): forbid placeholder events in create_event description
Reproducer (2026-04-29 dentist appointment): user said "this Friday, I have an appointment" with no other details. The model immediately called create_event with title="Appointment", description="User mentioned an appointment this Friday but hasn't provided details yet.", all_day=true. THEN it asked the user for time/location in its reply. When the user came back with "8am at my dentist for permanent crown fitting", the model called update_event — but never updated the title, leaving the placeholder "Appointment" in the calendar permanently. The bug isn't about the tool surface, it's that the model created an event before it had real content. The system prompt had no rule against this, so the model hedged: "log a placeholder, ask for details, then update". That pattern pollutes the calendar with garbage titles and forces immediate update_event calls. create_event tool description now includes an explicit anti-pattern: record a moment, ask for the missing pieces, and only call create_event once you have actual title + time + location. Stand-in titles like "Appointment" / "Meeting" / "Event" with "details TBD" descriptions are explicitly named as the failure mode. Pure prompt change. 18 tests pass; ruff clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9f8b451d15 |
fix(journal-prep): bucket tasks + drop non-proximate events (#159)
Two filtering issues that made the daily prep noisy and trained the
user to ignore it.
## Tasks: bucket into due-today / upcoming / overdue
The prep was calling `list_notes(due_before=day_date)` and labeling the
result as "tasks due today". That filter is strictly less-than, so it
returned only OVERDUE tasks (a single 68-day-stale task in this user's
case), while the prompt still framed them as fresh today's work. Each
day of the prep treated the same overdue task as new — the user
learned to ignore the line entirely.
`gather_daily_sections` now runs three queries:
- `tasks_due_today` — `due_after=day_date AND due_before=day_date+1`
- `tasks_upcoming` — next 7 days, exclusive of today
- `tasks_overdue` — strictly before today
Overdue entries carry a `days_overdue` count. `_render_sections_for_prompt`
emits three labeled headers ("TASKS DUE TODAY", "UPCOMING TASKS",
"OVERDUE TASKS (still on the list, not currently due)"). The system
prompt has a new TASK BUCKETS rule telling the model: don't call
overdue items "due today"; surface them with their staleness duration
("still on the list 68 days") and frame as a backlog reminder rather
than today's work.
Backwards-compat: `sections["tasks"]` still exists, now as the union
of all three buckets — strictly more useful than the prior overdue-
only behavior any frontend consumer was getting before.
## Events: tz-aware window + proximity filter
The user's "Birthday — 2026-09-29 (FREQ=YEARLY)" event was surfacing
in every daily prep, 5 months out. Root cause: `gather_daily_sections`
built `day_start`/`day_end` as NAIVE datetimes; `list_events` then
called `rrulestr(...).between(naive_from, naive_to)` against an
aware `dtstart`, which throws TypeError, hits the `except Exception`
fallback, and appends the canonical event row — regardless of whether
today is anywhere near a recurrence.
Fix:
1. Construct the day window as TZ-aware in the user's local timezone
and convert to UTC before the query. RRULE expansion now runs
correctly.
2. Defense-in-depth `_filter_proximate_events` drops events whose
start_dt is more than 7 days from `day_date` (in the user's local
TZ — not UTC, so a Friday 23:00 NY event isn't misclassified as
Saturday). If list_events ever leaks a far-future row again, the
prep doesn't surface it.
10 new tests in `tests/test_journal_prep_filtering.py` cover task
bucketing (overdue marker, due-today no-marker, no-due-date), the
proximity filter (the 4/29 reproducer, in-window keeps, local-vs-UTC
boundary, unparseable dates kept rather than suppressed), and the
rendering (overdue staleness shown, due-today doesn't repeat the date,
correct section ordering).
53 tests pass across journal_prep + journal_search + record_moment +
calendar_tool + events. Ruff clean.
Closes Fable task #159.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4f18023284 |
fix(journal): server-side guards on record_moment links + place names (#158)
Belt-and-suspenders to the prompt-layer changes in
|
||
|
|
6c309f1331 |
fix(journal): tune persona — capture-first, anti-overhelp, first-person moments (#157)
Three related rough edges in the journal voice surfaced from real journal usage 2026-04-27 → 2026-04-29: 1. **Persona overhelps.** When the user logged "today I'm prepping for an ISP migration at Branch 14 Bedford for work at famous supply", the assistant came back with "ISP migrations can be tricky. Are you handling the network configuration yourself, or is there a team supporting you? Also, are there any specific tasks or checks you need to complete before the switch?" — pushing IT-helpdesk advice the user didn't ask for. The user had to push back. JOURNAL_PERSONA now leads with "CAPTURE first, advise only if asked" and the RESPONSE STYLE block has an explicit anti-pattern banning troubleshooting / checklist / process-advice follow-ups unless the user explicitly invites them. 2. **Moments stored in third-person observer voice.** The dentist appointment beat got written as "The user mentioned having an appointment this Friday but hasn't provided details yet." — reads like an LLM transcript annotation, not a journal jot. The record_moment tool's `content` description previously said "in the user's voice or third-person", which was the literal source of the bug. New phrasing requires first-person/imperative with concrete GOOD/BAD examples, and the JOURNAL_CALIBRATION block reinforces it. 3. **Inconsistent emoji use.** 4/27 was clinical, 4/29 had 😊 and 🛠️ in the appointment confirmation. RESPONSE STYLE now bans emojis outright — the journal is a thinking-companion surface and the emoji warmth reads as out-of-register chat-bot tone. Bonus while in here: - New MOMENT ENTITY LINKING section explicitly forbids attaching a task_titles link unless the user references the task by name (the 4/27 Docker→ADHD auto-link bug; rest of that fix is in #158). - Same section rejects generic place placeholders ("work" / "home" / "office") in favor of letting the user name the real place. 22 tests pass (4 journal + 18 calendar tool); ruff clean. Closes Fable task #157. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
03d725ea3e |
fix(calendar-tool): anchor today's weekday in prompts + verify expected_weekday on create/update
A user asked Fable to schedule "this Friday at 8am" on Wednesday 4/29
2026. The model picked 4/30 (Thursday) and confidently labeled it
"Friday." The TZ pipeline did everything correctly given the model's
date — the bug was upstream: the model was guessing weekdays from ISO
dates without an anchor, and the calendar tools had no way to verify.
Three layered fixes:
1. **System prompts now name the weekday alongside the ISO date.**
Both the journal-conversation prompt and the general chat prompt
used to say "Today is 2026-04-29 (America/New_York)." They now say
"Today is Wednesday, 2026-04-29 (...)." LLMs are unreliable at
deriving weekday names from ISO dates; supplying the name removes
the guess.
2. **`expected_weekday` parameter on create_event / update_event.**
When the model passes `expected_weekday="friday"`, the backend
computes the resolved start_date's weekday in the user's local
timezone and rejects mismatches with a self-correcting error
("Date 2026-04-30 falls on Thursday, not Friday. Recompute..."),
without creating the event. The check is local-aware: a Friday
23:00 event in Tokyo crosses midnight UTC but the local view
stays Friday, and the validator respects that.
3. **Tool descriptions instruct echo-and-confirm.** create_event and
update_event descriptions now tell the model: when the user names
a weekday, state the resolved date in the reply BEFORE calling
the tool, and pass `expected_weekday`. Costs nothing in code,
reinforces the validator.
6 new tests — match success, mismatch rejection (with create/update
not invoked), omitted-param backcompat, invalid weekday name, local-
not-UTC weekday computation, and the update_event variant. All 18
calendar-tool tests + 33 event-related tests pass; ruff clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
611c940527 |
fix(calendar-tool): split start/end into date+time to make event creation TZ-durable
A user reported "next Friday at 8am" landing on the wrong day. The current `start` parameter accepts a combined ISO datetime string — when the model emits something like `"2026-05-01T00:00:00Z"`, the parser correctly honors the UTC tag and stores `2026-05-01 00:00 UTC`, which displays as `2026-04-30 19:00` for a UTC-5 user. The bug isn't in our parser; it's that we let the model TZ-tag the calendar day at all. The fix moves the foot-gun: `create_event` and `update_event` now prefer split fields (`start_date` + `start_time`, plus end variants). A `YYYY-MM-DD` string carries no TZ metadata for a model to mis-tag, and the backend builds the local datetime explicitly via `datetime.combine(date, time, tzinfo=user_tz).astimezone(UTC)`. Strict regex validation rejects anything with a TZ suffix on either field. The legacy combined `start` / `end` fields are kept as a fallback so saved tool-call payloads in conversation history still replay; new calls are steered toward the split shape via the tool description. 7 new regression tests cover Eastern, Pacific, Tokyo (positive offset), all-day inference, strict-shape rejection on both fields, backcompat with the legacy `start` field, and the same fix for `update_event`. 27 of the event-related tests pass; ruff clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3b2a0a119f |
chore(fable-mcp): bump version to 0.3.0 — journal tools replace briefing
The MCP server's briefing introspection tools were replaced with journal
equivalents in
|
||
|
|
a7de3296b3 |
docs(design-system): note Flutter app port
Adds a "Flutter app port — shipped 2026-04-28" section between the web Surface phase and Open threads. Records the two FabledApp commits (foundation 0f05f47, surface b9e68e3), explains the ActionColors ThemeExtension shape, points to reference call sites for the ActionColors.primary (calendar event Save) and ActionColors.destructive (confirm-Delete dialogs across notes/tasks/chat/calendar) patterns so downstream screens have a template to follow when reclassifying their own buttons opportunistically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
bc239119f3 |
fix: SettingsView TS errors + calendar month/year font
CI typecheck failed: JournalLocation interface requires `address: string`,
but Profile-tab Locations code created defaults like `{ label: 'Home' }`
without it. Symptoms were six TS2741 errors in SettingsView.vue.
Fix: include `address` in defaults and treat the user's place-name input
as the address field. Now homeQuery / workQuery sync to
locations.{home|work}.address — both at load time and on geocode.
loadJournalConfig now reads address (was reading label, which is fixed
to "Home"/"Work"); geocodeFor writes the typed query into address while
also setting lat/lon on success.
Calendar Month/Year title was actually rendering Fraunces, not Inter as
I'd claimed. FullCalendar renders .fc-toolbar-title as an <h2>, which
the global theme.css `h1, h2 { font-family: 'Fraunces' }` rule catches
before the parent .fc font-family inherit can do anything. At 1.1rem
it's below the doc's "Fraunces only at ≥18px" threshold, so explicit
Inter override on the deep selector.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
30dfbce426 |
fix(typography): strip chrome italic; reserve italic for emphasis
Per the design rule "italic is for emphasis, not for design", removed
every chrome `font-style: italic` declaration across the frontend.
42 declarations gone across 24 files: empty-state placeholder copy,
loading messages, "no results" hints, ghost text, voice/role labels,
field placeholder text, brand wordmark, et al.
Markdown content emphasis is unaffected — `<em>` and `<i>` tags from
`*emphasis*` markup still render italic via browser default styling
(prose.css doesn't override em behavior). User-typed emphasis in
notes, journal entries, and chat messages keeps its italic.
Specific spots that lost the decorative italic:
- KnowledgeView .filter-label, .empty-narrator
- NoteEditorView .ef-label, link-suggest related
- ChatPanel .empty-msg, .empty-greeting, .role-label
- ChatMessage .role-assistant .role-label (the "Fable" voice tag —
was italic per the doc's Illuminated Transcript spec, but per the
new typography rule the speaker tag stays regular and lets the
border-left + glow do the bubble framing on their own)
- AppHeader .brand-text ("Fabled" wordmark)
- editor-shared.css .title-input::placeholder
- ProjectView .project-title-input::placeholder
- HomeView .urgency-loading
- WorkspaceTaskPanel .empty-group
- WeatherCard .weather-unavailable
- SharedWithMeView .empty-msg
- DiffView empty-state spans
- ToolCallCard .tool-event-more
- SettingsView 7 spots (.you-label, .geo-pending, hint text, etc.)
- + several other empty-state / hint text spots
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
65ba1cc82a |
fix(typography): bump italic-Fraunces section/field labels to readable size
KnowledgeView's "Type" / "Tags" filter section headers (.filter-label) and NoteEditorView's entity-field labels (.ef-label — Email, Phone, Address etc on person/place notes) were using italic Fraunces accent at 0.72rem / 0.78rem. The italic + small + decorative-serif combo read as illegible flourish rather than functional section headers. Bumped: - .filter-label 0.72rem → 0.95rem, margin-bottom 6px → 8px - .ef-label 0.78rem → 0.92rem Italic Fraunces accent preserved (keeps the branded character that matches the rest of the surface). Just enlarged to a readable size. ChatMessage's .role-assistant .role-label kept at 0.8rem italic Fraunces — that's the doc's Illuminated Transcript voice label, a decorative speaker tag rather than a functional heading. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4cfea784a9 |
fix(projects): tab counter NaN when a status bucket is empty
services/projects.py:get_project_summary built task_counts dynamically
from a GROUP BY query, so a project with no done tasks would omit the
'done' key entirely. Frontend's TypeScript interface declares all three
lifecycle keys as required, and ProjectView.vue summed them to render
the Tasks tab counter — undefined + N = NaN.
Two fixes:
1. Backend: initialise task_counts with {todo: 0, in_progress: 0,
done: 0} so the service returns the contract its consumers expect.
Catches the same problem for HomeView's project widget and any
other consumer.
2. Frontend: defensive ?? 0 on the tab-counter sum, so the existing
deploy renders correctly even before the backend rolls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
016a2bd270 |
docs: clear briefing remnants and document journal system
The Briefing system was retired weeks ago and replaced with the conversational Journal, but several current-state docs still described it as live. Updates: architecture.md - settings keys: briefing_enabled / briefing_locations / office_days → journal_config (JSON: locations, temp_unit, prep schedule) - conversation_type: chat/briefing/mcp → chat/journal/mcp - briefing_date column → day_date on Conversation - "Briefing-Related Tables" section: dropped rss_feeds + rss_items rows (retired with PR #43); kept weather_cache and clarified that lat/lon now live in journal_config.locations, not on the cache row - routes/chat.py description: dropped trailing "briefing conversation routes" mention - routes/briefing.py → routes/journal.py with the current endpoint shape (config / today / day / days / weather / moments / trigger- prep) - services/briefing_pipeline + scheduler + conversations + profile → services/journal_prep + journal_pipeline + journal_scheduler + journal_search/moments + user_profile.build_profile_context features.md - "Daily Briefing" section rewritten as "Daily Journal": conversational, single morning prep, right rail with weather + upcoming events, profile-tab configuration, "what the assistant has learned" framing - Settings table: dropped Briefing tab row, added Profile tab row, fixed Notifications row's stale briefing-push reference api-reference.md - /api/briefing/* endpoints replaced with /api/journal/* — config, today/day/days, trigger-prep, weather (cached/current/refresh/ geocode), moments Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
39d56cfcf3 |
feat(settings): re-add journal config to Profile tab; clear briefing remnants
The Briefing Settings section was removed during the briefing→journal migration but its data (locations, temp unit, prep schedule) is still read by the journal backend. There was no UI to set any of it, so weather couldn't render and prep timing wasn't tunable. Re-adds the missing config inside the existing Profile tab — it's all "about the user" data and a separate Journal tab would just clutter the sidebar. New sections: Locations (after Work Schedule) - Home and Work place-name inputs with on-blur geocoding via /api/journal/weather/geocode - Temperature unit toggle (Celsius / Fahrenheit) - Status messages distinguish ok / pending / error Journal (before What the Assistant Has Learned) - Daily prep auto-generate toggle - Prep generation hour:minute (24-hour input) - Day rollover hour (so 1–3am entries still count as the previous day) - All controls disable cleanly when prep is off Cleanup - Profile "About You" desc: "chat and briefings" → "chat and the daily journal" - Profile "Interests" desc: "personalise news and briefing context" → "personalise the journal's daily prep and chat responses" - Profile "Work Schedule" desc: "Helps the briefing" → "Helps the journal" - Profile "What the Assistant Has Learned" desc: clarifies the summary is included in the journal's system prompt; observations come from journal + chat (not briefing) - General "Timezone" desc: "schedule briefings" → "schedule the daily journal prep" - Removed the dead `.briefing-*` CSS block (~190 lines of styles for retired briefing UI: feed-row, slot-row, add-feed-form, etc.) and replaced with fresh `.location-row`, `.unit-toggle`, `.unit-btn`, `.checkbox-label`, `.time-row`, `.time-input`, `.geo-msg` rules used by the new sections. unit-btn.active uses Moss action-primary per Hybrid; tokens flow through the rest. The "What the Assistant Has Learned" section was confirmed load-bearing for the journal — `journal_pipeline.py:139` calls `build_profile_context()` which feeds learned_summary plus other profile fields into the journal's system prompt. Not a remnant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3f287d7417 |
fix(design): brighten status-dot indicator lights
The "ready" / "cold" / "unavailable" dots in the AppHeader status indicator were pulling --color-success / --color-warning / --color-danger which after the foundation pass became Moss / Warning gold-brown / Error terracotta — all visibly muted. The green-ready dot in particular read too dark to register as a "ready light", and the pulse glow (bright emerald) had nothing to glow off of. Status dots are indicator *lights*, not semantic-palette UI elements. Decouple them with hardcoded vital values: - ready: #4ade80 (matches the existing pulse glow) - cold: #facc15 - red: #ef4444 Orange already used a hardcoded bright #f97316; left as-is. The rest of the system continues to use --color-success / --color-warning / --color-danger semantically (toast-success, validation errors, etc.) — only the indicator-light contexts get the brighter palette. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
64ab24864a |
docs(design-system): mark surface phase shipped end-to-end
Updates the Scribe-specific decisions section header and replaces the Surface-Phase TODO checklist with a per-PR ship table covering the seven PRs that landed today ( |
||
|
|
3c1ec4077f |
feat(design): surface phase PR 7 — edge surfaces polish
Per the spec, the final per-surface PR bundles the smaller / chrome-y / library-driven views (Header / Home / Calendar / Graph) together with EventSlideOver since it's the calendar's primary interaction surface. Button reclassification per Hybrid rule - CalendarView btn-new-event: accent gradient → Moss action-primary. Creating an event is a workflow action, not a brand moment. - EventSlideOver btn-primary (Save): accent gradient → Moss - EventSlideOver btn-secondary (Cancel/No): muted ghost → Bronze action-secondary - EventSlideOver btn-danger-ghost (Delete): hardcoded #ef4444 → --color-action-destructive ghost (with filled hover) - EventSlideOver btn-danger (confirm Delete): hardcoded #ef4444 → --color-action-destructive filled Brand-moment CTAs kept on accent - HomeView btn-workspace-hero: home-page hero CTA into the active project — central brand-moment surface, gradient stays - HomeView btn-workspace-sm: per-card workspace icon with the accent-tinted bg + hover-to-accent pattern, kept Two-weights-only - Snapped every font-weight 600/700 to 500 across the bundle (HomeView, GraphView, CalendarView, AppHeader, EventSlideOver). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
541e2ed713 |
feat(design): surface phase PR 6 — Settings polish
Settings is the densest button surface in the app. Per the surface-
phase spec, classify every button on a deliberate token.
Button reclassification per Hybrid rule
- btn-save (~13 instances across General/Account/Notifications/
Integrations/Data/Briefing/Admin tabs): flat accent → Moss
action-primary
- btn-primary (Groups tab "Save Retention", "+ New Group"): flat
accent → Moss action-primary
- btn-secondary (Detect timezone, Test SMTP, Refresh, Export, Add
slot, Preview voice, etc.): outline-on-bg-secondary +
hover-to-accent → Bronze action-secondary, filled
- btn-danger filled (Reset VAPID, Revoke API key Yes): added the
rule (was previously unstyled — relied on no-rule). Now Oxblood
action-destructive.
- btn-danger-outline (Invalidate sessions, Clear observations,
Delete personal data): generic --color-danger → Oxblood
action-destructive
- btn-danger-sm hover (group + member delete): --color-danger →
--color-action-destructive
- btn-delete row hover, btn-confirm-delete (user delete two-stage):
--color-danger → Oxblood. Confirm filled, cancel ghost.
- btn-toggle-open ("Open registration"): flat accent → Moss
- model-delete-btn hover: --color-danger → Oxblood
- btn-warn hover: muted-border-warning → filled-warning
Two-weights-only
- Snapped every font-weight 600/700 to 500.
Out of scope for this PR
- Voice/tone copy audit across labels and help text — per spec,
opportunistic only. Skipped.
- Settings row de-bordering — current section dividers are
structural; left as-is.
- Validation-state colors (.input-error etc.) keep --color-danger
(Error terracotta) per the doc — Error is for validation/error
messages, Oxblood is for destructive actions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ff498ce1a4 |
feat(design): surface phase PR 5 — Project + Workspace polish
Per the surface-phase spec for the Project list, Project view, and
3-panel Workspace cluster:
Button reclassification per Hybrid rule
- ProjectView:
- btn-workspace ("Open Workspace"): kept on accent gradient — the
workspace is the project's central feature moment, a Scribe-
flavored action, brand moment per the doc.
- btn-share: muted-outline → Bronze action-secondary
- btn-danger-outline (Delete project): generic-border + danger-on-
hover → Oxblood outline + filled hover (proper destructive)
- btn-save-panel: accent gradient → Moss action-primary
- btn-ms-confirm (milestone confirm): flat accent → Moss
- btn-ms-cancel: ghost → Bronze action-secondary
- .modal-btn-danger: --color-danger → --color-action-destructive
- ProjectListView:
- btn-primary ("+ New Project"): flat accent → Moss action-primary.
Empty-state CTA (.empty-action) keeps accent — that *is* a
brand moment per Hybrid, "create your first project".
- WorkspaceTaskPanel + WorkspaceNoteEditor:
- btn-delete-task / btn-delete: hover --color-danger → Oxblood
- btn-delete-confirm / btn-confirm-delete: --color-danger pair →
--color-action-destructive pair, with filled hover for stronger
affordance
- btn-save (WorkspaceNoteEditor): flat accent → Moss
- WorkspaceChatWidget:
- History toggle: replaced unicode ▾ with Lucide ChevronDown
(PR 1 carry-over for emoji-as-icon)
Two-weights-only
- Snapped every font-weight 600/700 to 500 across the cluster.
Borders kept (per structural-not-decorative rule)
- Milestone group cards: standalone containers, keep
- Project cards in list: standalone containers, keep
- Task slide-over panel: drawer modal, keep
- Workspace 3-panel separators: major section dividers, keep
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
efb3534f3a |
feat(design): surface phase PR 4 — Knowledge cluster polish
Per the surface-phase spec for the Notes/Tasks viewers and editors:
Long-form line-height
- prose.css: bumped global .prose from 1.6 to 1.7. Applies to Note
viewer body, Task viewer body, anywhere markdown renders into a
reading surface. Chat assistant bubble already had the explicit
override; now consistent with the rest.
Button reclassification per Hybrid rule
- Shared editor-shared.css:
- btn-save: accent gradient → Moss (action-primary). Saving is
"operating the software", not a brand moment.
- btn-delete: --color-danger (Error terracotta) → Oxblood
(action-destructive). Layout updated for inline-flex so the
Trash2 icon at call sites lines up alongside the label.
- NoteEditorView, TaskEditorView: Delete buttons now contain a
Trash2 icon per Hybrid's "destructive paired with icon" rule.
- NoteViewerView, TaskViewerView:
- btn-edit (and TaskViewer's btn-advance): accent gradient → Moss.
Switching to edit / advancing status are workflow actions.
- btn-convert, btn-share: ghost-on-hover-to-accent → Bronze
action-secondary (alternate paths).
Two-weights-only
- Snapped every font-weight: 600/700 to 500 across editor-shared.css
and the five Knowledge-cluster views.
Out of scope for this PR (deliberate punt)
- Smaller utility buttons (.btn-suggest-tags, .btn-link-all,
.btn-add-subtask, the AI-assist generate/proofread/accept/reject
set, etc.) — currently ghost-styled, generally compliant. Will
revisit only if they read off in practice.
- Filter chip / sub-task list-row border audit — deferred since
current styles already lean on background tint for affordance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4192a64c0f |
feat(design): surface phase PR 3 — Chat surface polish
Per the surface-phase spec for the Chat cluster (ChatView, ChatPanel, ChatMessage, ChatInputBar, ToolCallCard): Long-form line-height - ChatMessage: assistant-bubble .message-content jumps from 1.55 to 1.7 — chat is a reading surface, not a snippet stream. User bubbles stay tighter. - JournalView: dropped its :deep(.role-assistant .message-content) override; ChatMessage handles it now and Journal inherits. ToolCallCard borders - Removed the outer 1px border. ToolCallCard always renders inside an assistant bubble; the bubble already contains it. Background tint differentiates without re-bordering. Per the structural-not-decorative rule. - Error state preserved as a 3px left-edge accent in --color-danger, mirroring the assistant bubble's own left-edge pattern. Button reclassification per Hybrid rule - ChatView .bulk-link "All"/"None": accent text → --color-text-secondary (these are tertiary list-control affordances, not brand moments) - ChatView .bulk-delete-btn: --color-danger (Error terracotta) → --color-action-destructive (Oxblood) per Hybrid; paired with a Trash2 icon since destructive should always be reinforced by an icon, not just color - ChatView .btn-delete-conv hover: same Error → Oxblood swap - .btn-new-conv stays accent (brand moment, correct already) - .btn-send stays accent gradient (primary brand moment, foundation) Two-weights-only - Snapped every font-weight: 600/700 to 500 across ChatView, ChatPanel, ChatMessage, ToolCallCard per the doc rule. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f20e54a068 |
fix(journal): widen events window to match prep's framing
The right rail's loadEvents was using "now → now + 14 days", which filtered out events earlier today that had already passed. The prep uses "today 00:00 → today 23:59" so it includes those events and explicitly notes they're "in the past". The two surfaces talked about different sets of events. Widen the right rail to "today 00:00 → today + 14 days 23:59" so events from earlier today still surface and group under "Today". The prep and the right rail now reference the same set within today. Events further out (next 14 days) keep working as before. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
de4b1d7c7e |
fix(weather): match prep behavior — serve cached weather regardless of age
The /api/journal/weather route was filtering out cache rows older than 24 hours via parse_weather_card_data, while journal_prep.py read the same rows raw without freshness checking. Result: the daily prep referenced "home" and "work" temperatures while the right-rail UI showed nothing — two surfaces, same backing data, inconsistent visibility. Two changes: 1. parse_weather_card_data no longer returns None for stale data. WeatherCard already exposes fetched_at and gracefully hides today_high / forecast fields when they're absent, so old data renders with whatever fields the cached forecast still covers. 2. The /weather route opportunistically schedules a background refresh for any cache row older than 4 hours. If the user's journal_config has lat/lon for that location_key, the refresh runs and the next page load gets fresh data; if no usable config, the refresh is a silent no-op and the stale cache is still served. This makes prep and UI consistent. It also self-heals over time — once locations are configured, stale caches get refreshed on the next page load instead of waiting indefinitely. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3d916d7357 |
feat(design): surface phase PR 2 — Journal polish
Per the surface-phase spec for Journal: - Weather refresh button: ↻ unicode → Lucide RotateCcw (PR 1 carry-over for emoji-as-icon) - .journal-title: dropped redundant font-family override (h1 inherits Fraunces from theme.css); also snapped weight 700 → 500 per doc's "two weights only" rule - .weather-tab.active, .events-day-label, .event-title, .panel-label: snapped 600/700 → 500 per same rule - Added long-form 1.7 line-height to journal-chat-panel assistant bubble content (the daily prep is prose, not chat snippets) - Removed dead .news-section / .news-card / .news-* / .reaction-btn CSS — news functionality was retired with the briefing→journal migration (PR #43); styles were never cleaned up Buttons audited per Hybrid rule: btn-trigger ("Refresh prep") and weather-refresh-btn are tertiary actions, already correctly styled as Pewter ghost. .btn-send (Journal Send) flows through ChatInputBar on the accent gradient — brand moment, kept as-is. Borders audited per structural-not-decorative: header bottom, sidebar left, section dividers, form input borders all kept (all genuinely structural). No decorative borders to remove. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
df423ab906 |
fix(task-editor): prevent preview pane and work log from flex-shrinking
.task-main is a flex column. Children default to flex-shrink: 1, so when body content is taller than the available column height, flex squeezes .preview-pane back to its min-height (200px) and the overflow renders visibly on top of subsequent siblings — making the TaskLogSection appear in the middle of the body content. Surfaced by viewing a task with an unusually long markdown body in preview mode. Pre-existed PR 1, just rarely hit. NoteEditorView uses the standard block-flow .editor-main and isn't affected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d023209f13 |
chore(deps): regenerate package-lock for lucide-vue-next
CI's npm ci step requires lock and package.json to be in sync. Adds the lucide-vue-next ^0.469.0 entry plus the indirect @esbuild platform variants that npm pulls in on lockfile refresh. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
93a3beb5d6 |
feat(design): surface phase PR 1 — Lucide icon migration + scale enforcement
Replaces every hand-inlined SVG in the chrome (60 across 15 files) with Lucide components. Snaps icon sizes to the doc's 16/24 scale — all icons in this pass land at 16. AppLogo wordmark stays as the legitimate "custom app icon" exception per the doc; GraphView's <svg> mount point for D3 stays as well. Replaces emoji-as-icons in clear button-affordance cases with their Lucide equivalents: ✕ → X (close/dismiss buttons across 8 files), ✓ → Check (ProjectView task-advance button), 🎤 → Mic (ChatPanel voice CTA), 📎 → Paperclip (ChatView context toggle), ↑ → ArrowUp (ChatInputBar Send), × → X (ChatPanel context-note remove), ☀/☾ → Sun/Moon (AppHeader theme toggle). Inline emoji punctuation in textual confirmation copy ("Saved ✓", "Created ✓", `done: "✓"` status maps, `'✓' : '📄'` interpolations) is left for surface-phase voice/tone touchups — replacing requires structural template changes and is best done per-component. Stroke weight stays at Lucide's default 2px; tightening to the doc's 1.5/1 is deferred per the surface-phase spec (option ii: drop-in + scale enforcement). Adds lucide-vue-next ^0.469.0 to frontend/package.json. Docker rebuild handles the install. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
54710b35e9 |
docs(design-system): note foundation pass shipped; expand surface-phase thread
Updates the "Scribe-specific decisions in progress" header to reflect
that the foundation pass landed in
|
||
|
|
7a9a8b7819 |
feat(design): foundation pass — dusty violet palette, warm parchment light, Inter + JetBrains Mono
Applies the FabledSword + Scribe iteration palette to theme.css end to end: indigo (#7c3aed) → dusty violet (#5B4A8A) accent, cool grey → Obsidian/Iron/Pewter dark surfaces, warm parchment (#F5F1E8) light mode, Inter body + JetBrains Mono code loaded alongside Fraunces, and neutral hairline scrollbars (chrome is structural, not branded). Adds the action token set (--color-action-primary Moss, --color-action-secondary Bronze, --color-action-destructive Oxblood, --color-action-ghost-border Pewter) but does not yet reclassify any buttons — surface-phase work. Buttons remain dusty-violet gradients in the meantime, by design. Removes deprecated --color-accent-warm; replaces concrete usages with --color-text-secondary (dates, flavor copy) or --color-warning (paused status). Sweeps hardcoded indigo literals in component scoped CSS so they don't bypass the token system. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
057b30b89e | Merge pull request 'Release v26.04.27 — Journal replaces Briefing; RSS retired; Scribe rebrand' (#43) from dev into main v26.04.27.1 | ||
|
|
a3071a53cb |
feat(routing): land on Journal at root; move Knowledge to /knowledge
Routing changes: - / now redirects to /journal (Journal becomes the home view) - /knowledge becomes a real route pointing at KnowledgeView (was a redirect to /) - /notes redirect target updated from / to /knowledge (the Knowledge surface, which is where the notes-related dashboard lives) To revert (Knowledge as home): change the redirect target on the / route back to "/knowledge". Knowledge stays a real route either way, so the swap is a one-line edit. Nav: Knowledge link in AppHeader now points to /knowledge. The "active" check simplified to route.path === "/knowledge" since / is no longer Knowledge's home. The brand logo link stays at "/" — clicks still go "home", which is now Journal. Keyboard shortcuts in App.vue (Escape, g h, g t) still navigate to "/" and correctly land on Journal via the redirect — no change needed there. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4faaa5246b |
fix(journal): make record_moment mandatory, not a "nice to have"
Inspection showed only ONE record_moment call across the entire day's journal — and that one had hallucinated person_ids. Multiple clear beats went uncaptured: AP installation, going to watch a show with daughter, decompressing-with-game. The prior calibration said "use record_moment freely for meaningful beats" — too soft. The model treated it as optional, especially when already in chatbot-reply mode. Rewritten: record_moment is now framed as the model's PRIMARY JOB. The calibration includes an explicit checklist of what counts as a beat (event, encounter, decision, observation, plan, feeling, accomplishment) and an explicit instruction to call record_moment FIRST, before composing the reply. Multiple beats → multiple calls. The ONLY skip case spelled out: purely meta-conversational messages (acknowledgements, meta-asks about prior tool results). Tests on a fresh conversation will tell us if this moves the needle — today's journal is poisoned by ten prior chatbot-flavored turns that the model is pattern-matching against in its own history. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0ed9cbf666 |
fix(journal): restore prep prose; soften persona toward chat-like
Per user clarification: previous over-rotation dropped the LLM-generated
prep prose entirely (just a phase greeting) and made the chat persona
extremely sparse ("you are a place where words go down"). User actually
wanted only the chat replies pulled back, NOT the prep dropped, and the
chat to behave largely like normal /chat — asking follow-ups and
verifying earlier details.
services/journal_prep.py — restored:
- _render_sections_for_prompt
- _PREP_SYSTEM_PROMPT (the direct, briefing-style prompt from
|
||
|
|
4668c0950b |
fix(journal): minimal prep + quieter persona
The prep was generating a multi-sentence recap of tasks/events/weather/
projects/recent moments via an LLM call. Per user direction, that's
redundant — the right-side widgets already show today's data — and the
verbosity made the journal feel chatty when the user wanted quiet.
Replaces the prep prose generator with a single phase-aware check-in
question (drawn from a static map: morning="How are you starting the
day?", midday="How's it going so far?", evening="How did the day shake
out?"). No LLM call. The structured `sections` are still gathered and
persisted on msg_metadata for provenance and possible future tooling
(e.g., search), they just don't render in the prep message.
Also pulls the journal persona way back. The prior framing pushed the
model toward stock therapy-template patterns ("I'm sorry you're feeling…"
+ numbered option lists). The new persona is "you are the user's journal —
listen, be quiet, stay out of the way." RESPONSE STYLE rules now lead the
calibration block and explicitly forbid:
- apologizing for the user's feelings
- offering to help / pitching tools
- multi-option menus
- verbatim repetition of prior replies
- padding short replies into paragraphs
Most replies should be one short sentence. Sometimes the right reply is
"Got it" + a record_moment tool call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
b728acd841 |
fix(journal): name-based entity resolution + tighter calibration + anti-repetition
Three real bugs surfaced from inspecting today's journal turns:
1. record_moment was getting fed hallucinated person_ids (the LLM passed
[1, 2] instead of the IDs save_person had just returned). Result: the
moment was linked to two random old test-data notes ("test task 2",
"Tell a joke"), not the people the user actually mentioned.
2. The calibration rule "ask before save_person" was being silently
ignored — model just called save_person on first mention of Victoria
and Mother without asking the user.
3. The model produced a verbatim-identical reply to its previous turn when
the user mentioned "overwhelmed" twice — same numbered-list of 4
options, same closing line. The "warm listener / ask gentle questions"
persona was pushing toward stock therapy-template patterns.
Fixes:
services/tools/journal.py — record_moment now accepts *_names parameters
(person_names, place_names, task_titles, note_titles). Server resolves
each name to a note ID via case-insensitive title match, scoped by
note_type or task-status. *_ids parameters still exist but are now
documented as DISCOURAGED. The LLM physically cannot invent the wrong ID
when using names — names with no match are silently dropped. Resolution
happens via _resolve_entity_ids_by_name helper.
services/journal_pipeline.py — JOURNAL_PERSONA tightened (no more
"warm/curious listener" framing that pushed toward stock comfort
patterns). JOURNAL_CALIBRATION rewritten as scannable sections with
imperative language: PEOPLE/PLACES require asking before save_person;
TASK/NOTE state changes use the confirmation flow; MOMENTS are silent
but MUST use *_names not *_ids; OTHER notes the no-set_rag_scope and
no-auto-notes invariants. Added a RESPONSE STYLE section that explicitly
forbids verbatim repetition and stock multi-option menus.
After deploy, force-regenerate today's prep via fable_trigger_journal_prep
to also pick up the tighter prep prompt from
|
||
|
|
c5498273c3 |
feat(mcp): replace briefing tools with journal tools
The MCP was still calling /api/briefing/* endpoints I deleted in the
journal hard-cut. Replaced the briefing tool surface with a journal
equivalent so external MCP clients can inspect and control the journal
the same way they could the briefing.
Changes:
- New fable_mcp/tools/journal.py — helpers for /api/journal/* endpoints
- Delete fable_mcp/tools/briefing.py — RSS endpoints are gone too
- server.py: drop fable_list_rss_feeds, fable_add_rss_feed, fable_remove_rss_feed,
fable_list_briefings, fable_get_today_briefing, fable_get_briefing_messages,
fable_trigger_briefing, fable_reset_today_briefing
- server.py: add fable_get_today_journal, fable_get_journal_day,
fable_list_journal_days, fable_trigger_journal_prep, fable_get_journal_config,
fable_list_moments
- server.py: fable_get_conversation now points at journal.get_conversation
(same /api/chat/conversations/{id} endpoint, just lives under journal helpers)
- Update _INSTRUCTIONS to describe the journal model (replacing the RSS/briefing
section) — explains the daily prep, moments, day payloads
- Update top-line docstring: "Fable Assistant" → "Fable Scribe"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
590a07bc13 |
fix(journal): tighten prep prompt — direct briefing, not flowery letter
Previous system prompt asked for "warm, conversational, like a friend writing a letter" which produced flowery preludes that buried the actual data. Rewritten to: - Lead with practical data (tasks, events, weather) — concrete and specific - 4-7 sentences total, tight prose, no padding - Recent moments / open threads mentioned briefly at the END as context, not as the lead - Voice: "competent assistant briefing the user" not "friend writing a letter" - Close with a short journal invitation under 8 words Also dropped max_tokens 600 -> 400 to bias toward concision. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c9f2134ad4 |
feat(journal): conversational LLM-generated daily prep (replaces card)
The structured prep card was data-rich but voiceless. Replaced with an LLM-generated conversational opener — same shape the briefing's compilation slot had — that renders as a normal assistant chat bubble at the top of the day's conversation. Backend (services/journal_prep.py): - Renamed generate_daily_prep -> gather_daily_sections (still pure data fetching, no LLM); kept the old name as a backwards-compat alias. - New _generate_prep_prose: hands the gathered sections to generate_completion with a warm-conversational system prompt; returns prose. Falls back to a plain greeting if the LLM call fails or no model is configured. - ensure_daily_prep_message now persists the prep as role='assistant' with the prose as content. Structured sections stay on msg_metadata for provenance. Auto-upgrades legacy system-role preps in place on next call. Frontend: - Drop the <article class="daily-prep"> structured block from JournalView. The prep is now just the first chat bubble — picks up the existing Illuminated Transcript styling automatically. - Drop dayMessages / prepMessage / prepSections / asArray helpers — no longer needed. - ChatMessage hideMessage filter: comment refined to clarify it only catches LEGACY system-role prep rows. Current preps are assistant-role and render normally. Net effect: open /journal -> first thing you see is a warm assistant bubble that talks about your day -> input bar below to reply. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
dda62265c9 |
fix(journal): drop dangling JournalSetupWizard import
The setup-wizard component was deleted in the RSS hard-cut (it was briefing-config-shaped), but JournalView still imported it — breaking the Vite build. Removed the import + showWizard / wizardChecked / checkSetup / onWizardDone plumbing. JournalView now mounts straight into the day view. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
297f2252a3 |
fix(test): repoint lookup tests to article_fetcher.fetch_article_text
The two test_lookup_tool.py cases were patching the now-deleted fabledassistant.services.rss._fetch_full_article. The trafilatura URL→text helper moved to services/article_fetcher in the RSS hard-cut. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
dbd9f00061 |
refactor: hard-cut RSS infrastructure (scope C)
Removes the entire RSS feature surface — feeds, items, embeddings, reactions, discussion-note flow, briefing news context, settings, env-vars, and DB tables. Keeps the URL-generic article-reader (the read_article LLM tool) under a clean module so the LLM can still fetch arbitrary article content from URLs the user provides. Backend: - New services/article_fetcher.py — single source of trafilatura URL→text - New services/tools/article.py — read_article tool (was nested under tools/rss) - Delete services/rss.py, rss_classifier.py, rss_filtering.py, article_context.py - Delete services/tools/rss.py - Delete models/rss_feed.py (RssFeed, RssItem), models/rss_item_embedding.py - services/embeddings.py: drop upsert/semantic_search/backfill RSS helpers - services/llm.py: remove _build_briefing_article_context, briefing-conv branch, ARTICLE_DISCUSS_SEED skip-RAG branch; drop get_rss_items / add_rss_feed from the actions list - services/generation_task.py: drop _maybe_save_article_discussion_note + caller - routes/chat.py: drop /api/chat/from-article/<id> endpoint - routes/journal.py: re-import via web.py refactor (article_fetcher path) - services/tools/__init__.py: register `article`, drop `rss` - services/tools/_registry.py: drop the requires=='rss' check - app.py: drop backfill_rss_item_embeddings + backfill_rss_article_content tasks - config.py: prose-only edit (no env var change — RSS env vars were never first-class) Frontend: - stores/settings.ts: drop rssEnabled - SettingsView.vue: drop the RSS-classification mention - api/client.ts: drop openArticleInChat (the from-article endpoint is gone) Tests: - Delete tests/test_rss_service.py, test_news_api.py, test_article_reading.py Migration: - 0042_drop_rss: DROP TABLE rss_item_embeddings, rss_item_reactions, rss_items, rss_feeds; DELETE settings rows for rss_enabled / briefing_*_topics Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cacfcac86a |
fix(journal): restore weather + events panels; hide daily-prep system msg in chat
The journal UI was over-stripped earlier — weather panel, current-conditions poll, and the upcoming-events sidebar were all dropped. Restored those (calls the new /api/journal/weather, /api/journal/weather/current, /api/journal/weather/refresh, /api/journal/weather/geocode endpoints). Also: the daily-prep system message was rendering as flat text inside ChatPanel because there's no .role-system bubble styling. Added a hideMessage guard in ChatMessage so daily-prep system messages don't render in the chat stream — they're already shown above as a structured prep card. News / RSS reactions / article-discuss are intentionally NOT in the journal (scoped out per user direction). The broader RSS infrastructure cleanup is a separate, larger task. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
873e70c7ea |
feat(journal): add JournalView UI with /journal route + nav link
Restores a working browser surface for the journal feature, which was left UI-less when Stage F of the original plan was deferred. JournalView is a fresh write (not a rename of BriefingView) — drops the briefing- specific weather panel, news cards, RSS reactions, article-discuss button, and setup wizard, since none of those have journal-backend equivalents. What it does: - Fetches /api/journal/today on mount; shows today's daily-prep card (rendered from msg_metadata.kind === 'daily_prep' sections) - Day picker via /api/journal/days lets you switch to past days - Refresh-prep button hits /api/journal/trigger-prep - Center is the existing ChatPanel pinned to today's journal conversation (so the chat input + SSE + tool-call cards inherit unchanged) - Right sidebar keeps the upcoming-events list (uses the generic /api/events endpoint, not a briefing-specific one) Also: - Replace the dead Briefing API client functions with Journal equivalents (getJournalConfig, saveJournalConfig, getJournalToday, getJournalDay, getJournalDays, triggerJournalPrep, list/update/deleteJournalMoment). - Remove NewsView.vue — it was orphaned (no route, no nav) and depended on the deleted /api/briefing/* endpoints. If a standalone news surface is wanted later it'll need to be rebuilt against new endpoints. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0c91ab026d |
docs: add FabledSword design system + Scribe iteration decisions
Captures the FabledSword baseline (verbatim from initial draft) plus the Scribe-specific decisions made during the first iteration pass: - Hybrid accent rule (accent reserved for brand moments + nav/cursor + tags/wikilinks/in-progress/focus rings; Moss/Bronze/Oxblood/Pewter for action buttons by default) - Light mode warm parchment (specific hex values pinned) - Status + priority palette extension table (status-paused added) - Typography: Inter for body, doc scale verbatim, two weights - Chat-bubble "Illuminated Transcript" pattern codified - Voice and tone: adopt principles, defer formal audit - Border philosophy: structural not decorative - Iconography: Lucide as source, strict 16/24 scale, 1.5/1 stroke - Warm gold accent dropped (dates → text-secondary, paused → Warning) Doc lives at docs/design-system.md. Polish pass (applying the system to existing UI) is a separate, deferred phase. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ce76a003f7 |
feat(journal): /api/journal/* routes blueprint + cosine helper unit tests
Endpoints: - GET/PUT /api/journal/config — per-user journal config - GET /api/journal/today — today's journal conversation, generates daily prep on demand - GET /api/journal/day/<iso_date> — past day's journal - GET /api/journal/days — list of dates with journal content - POST /api/journal/trigger-prep — manual regeneration of prep - GET /api/journal/moments — list/search moments with filters - PATCH /api/journal/moments/<id> — edit content/tags/pinned + junctions - DELETE /api/journal/moments/<id> Blueprint registered in app.py. tests/test_journal_search.py — cosine helper coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |