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>
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>
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 590a07b)
- _generate_prep_prose
- _fallback_prep_text
- ensure_daily_prep_message now calls _generate_prep_prose again
- removed _phase_for_now / _phase_prompt helpers (no longer needed)
services/journal_pipeline.py — persona rewritten:
- Old: "You are the user's journal. Be quiet. Listen. You are not helpful."
- New: "You are the user's assistant. Behave like the rest of the app's
chat: respond conversationally, ask follow-up questions, verify details
from earlier turns, use tools naturally."
- Calibration block reorganized: PEOPLE/PLACES (ask first), MOMENTS
(silent + use *_names), STATE-CHANGING TOOLS (confirmation flow),
OTHER, RESPONSE STYLE.
- RESPONSE STYLE keeps the no-apologizing / no-option-menus /
no-verbatim-repetition / match-user-length rules but drops the "be
quiet, one short sentence" framing.
Net behavior:
- Open journal → LLM-generated prep prose with today's tasks/events/weather
- Reply → assistant responds conversationally like /chat, asks follow-ups,
verifies details, uses tools
- Background: silently records moments via *_names, asks before creating
new people/places
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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 590a07b.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- services/tools/journal.py — record_moment + search_journal tool handlers
- services/tools/_registry.py: add `journal` flag on ToolDef + tool() decorator
- get_tools_for_user(user_id, conversation_type='chat'|'journal') —
exclude journal-only tools from chat sessions; exclude set_rag_scope
from journal sessions
- services/tools/__init__.py: register the new journal module; drop the
unused get_briefing_tools export
- services/llm.py build_context: short-circuit for journal conversations,
using journal_pipeline.build_journal_system_prompt and skipping all
notes-RAG injection (preserves the journal/notes isolation invariant)
- services/generation_task.py: pass conversation_type into get_tools_for_user
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Also rename services/tz.user_briefing_date → user_day_date with a backwards
compat alias (briefing modules using the old name will be deleted in the
upcoming briefing tear-down stage). Update services/chat.py to_dict to use
day_date.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Derive REPO_ROOT from the script's own location instead of hardcoding
the absolute project path, so the hook keeps working if the project
directory is renamed or moved.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates user-visible branding across frontend (PWA manifest, page title,
Settings, push fallback), backend (email templates and subjects, LLM
system prompt, CalDAV displayname, SMTP from-name default), README,
quickstart compose, and MCP server description.
Also updates the CI image path and quickstart image reference to
git.fabledsword.com/bvandeusen/fabledscribe in preparation for the
Forgejo repo rename. Internal Python package, env vars, and database
schema unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Title input bumps to 1.4rem with Fraunces serif (matches the
design language's heading treatment) and gains 0.9/1.1rem padding
on the title row for breathing room.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drop border-bottom lines between header / title / tags / toolbar
so they read as one continuous input surface.
- Add a single border-top above the editor body so the body's
existing boundary stays clear.
- Notes rail gets --color-bg-card so it no longer visually merges
with the tasks panel (both previously shared --color-surface).
Dropped the rail's border-right since the tint now carries the
division from the editor pane.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Widget is now a bottom-anchored dock spanning the content width
(max-width 1200px, rounded top corners, bg-secondary, upward shadow)
instead of a floating corner box.
- Drop the closed state and ✕ button — widget is always present,
matching KnowledgeView's pattern. Collapsed = input-only dock,
expanded = full ChatPanel above the input.
- Grid ratio 1fr:2fr → 0.85fr:2.15fr so tasks get a touch less room
and notes a touch more.
- Workspace note-rail (the notes picker column inside the editor)
widens from 155px to 200px so note titles breathe.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Clicking the ▾ button reveals conversations where
rag_project_id === projectId, most-recent first. Selecting one
repoints the widget (and the workspace_conv_{pid} storage key) to
that conversation without deleting the previously active one.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WorkspaceView now renders tasks (1fr) on the left and notes (2fr) on
the right with the floating WorkspaceChatWidget layered on top. The
widget owns the SSE streamingToolCalls watcher and emits note-changed
and task-changed events; the view listens and reloads the affected
panel refs.
Dropped from the view:
- ChatPanel mount, empty-state quick chips, and Chat header toggle
- The conversation lifecycle + SSE watcher (moved into the widget)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Modeled on KnowledgeView's mini-chat. Three states (closed handle,
collapsed input-only, expanded full panel). Takes over the project
conversation lifecycle from WorkspaceView (resume-or-create on mount,
delete-if-empty-and-new on unmount) and adds a restart action. Reuses
ChatPanel for expanded view and ChatInputBar for collapsed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
trafilatura.extract dispatched via run_in_executor isn't safe to run
concurrently — two parallel calls can crash the process with a
libxml2-level double free. The top-level Wikipedia+SearXNG gather is
fine; only the inner per-article extraction needs to stay sequential,
matching the pre-parallelization behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runs the Wikipedia summary and SearXNG search concurrently and returns
both when available, so current-event questions aren't masked by a
generic role article from Wikipedia. When the Wikipedia summary includes
a thumbnail, it is cached through the existing image pipeline and
surfaced as an embeddable markdown snippet alongside the extract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runs wiki_search in parallel with SearXNG queries; Wikipedia results
(which already carry content via their extract field) are merged into
the source pool before outline generation, skipping a separate fetch
step. Also fixes a pre-existing F811 ruff violation in the test file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetch and display the next 14 days of events in the briefing right column,
grouped by day with color dots, time, and location. Fills the empty space
below the weather card.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a refresh button (↻) to the weather section header in BriefingView so
users can manually re-fetch weather data (needed after deploying the hourly
precip changes to populate the cache). Update the existing POST
/api/briefing/weather/refresh endpoint to return full card data instead of
just location keys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetch hourly precipitation probabilities from Open-Meteo alongside daily
forecasts. Generate human-readable precip summaries ("Rain likely 2–5 PM",
"Rain likely all day") for today and each forecast day. Display today's
summary as a styled callout and show peak precipitation hour in forecast rows.
Also fix briefing pipeline to parse all weather location rows (not just first).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rss_enabled check in _check_requires now calls get_setting, which
needs a database connection. Mock it in the test that exercises
get_tools_for_user.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RSS is now off by default. When disabled:
- Scheduler skips RSS feed sync during compilation slot
- Briefing pipeline skips RSS item gathering
- RSS LLM tools (get_rss_items, add_rss_feed) are hidden
- API routes return empty results for feeds/news
- Frontend hides News nav link, RSS Feeds and News Preferences in settings
- Briefing view hides news sidebar section
Toggle in Settings > Briefing > RSS / News.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add framing preamble to auto-injected notes so the model treats them as
reference material rather than user input. Remove RSS semantic search
injection from all chat conversations — the discuss tool handles that need.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>