Spaces:
Runtime error
Runtime error
Commit History
-
chat: bottom input matches hero — floating card, text-xl, Send label
webmuppet +1: Claude Sonnet 4.6Replaces the compact dark footer bar with a floating rounded card (same rgba glass style as the hero input) centred at max-w-3xl. Textarea grows to rows=2, text-xl with transparent background. Stop/Send buttons gain a text label to match the hero variant. A gradient fade from #0F0027 to transparent behind the card prevents content bleeding through. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>ac3c645 -
chat: cycle loading messages through pipeline phases
webmuppet +1: Claude Sonnet 4.6Pre-token (retrieval phase): cycles through 5 messages every 2.5s — 'Looking at the legislation', 'Reviewing relevant regulations', 'Assessing requirements', 'Scanning specific guidelines', 'Understanding policy recommendations'. Post-first-token (generation phase): shows 'Generating tailored response' + small dots above the streaming markdown, disappears when the response completes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>c812b81 -
sidebar: show full question text for conversations and saved responses
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>741c3dc -
chat: constrain thread to max-w-3xl centred column
webmuppet +1: Claude Sonnet 4.6Wraps the message list in a max-w-3xl mx-auto container matching the bottom input bar width — same breakpoint Claude.ai uses (~768px). Assistant bubbles now go full width within that column rather than being additionally constrained to 80%. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>2befd58 -
Dockerfile: chown venv to user after uv sync
webmuppet +1: Claude Sonnet 4.6uv sync runs as root and creates .venv owned by root. HF Spaces then starts the container as user (UID 1000) and uv run fails with Permission denied when trying to write to site-packages. Fix: add chown -R user:user after all build steps, before USER user. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>62aed37
-
docs: update CLAUDE.md for React+FastAPI refactor
webmuppet +1: Claude Sonnet 4.6Rewrites the status, picking-up, repo layout, conventions, deployment, and browser persistence sections to reflect the refactor branch reality: React+Vite frontend, FastAPI server, language selector wired end-to-end, ECE_MODEL=sonnet required in .env for local dev. Corpus/extraction/content sections unchanged — still accurate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>1eb4528 -
Fix language selector: wire user choice through to generator
webmuppet +1: Claude Sonnet 4.6Three-layer fix: - pipeline.py: run_query_retrieval accepts an explicit language param; uses it for the response language while still auto-detecting the source language for query translation (can't translate from 'mi' if the text is English) - generator.py: language instruction now says 'user has selected X as their response language' rather than 'user asked in another language', which was wrong when the query was typed in English; imports get_language_name for the human-readable label - server.py: passes body.language through to run_query_retrieval - prompts/generator_system.md: clarifies that an explicit language instruction in the messages takes precedence over the English default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>016503a -
chat-ui: profession-specific starter pills
webmuppet +1: Claude Sonnet 4.6Generic questions shown by default; selecting a profession from the sidebar immediately swaps the pills to four questions relevant to that practitioner type and their specific council/board rules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>22bd5ae -
chat-ui: stop button replaces send while streaming
webmuppet +1: Claude Sonnet 4.6useStream exposes abort() which cancels the in-flight fetch via AbortController. ChatInput bottom variant swaps the send icon for a filled stop square while isStreaming is true. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>e4e4267 -
chat-ui: remove 'Other' from profession list
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>9f06f9f -
chat-ui: profession select default option → '-- please select --'
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>f4e0810 -
chat-ui: move new conversation button above saved responses in bottom block
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>a7e286b -
chat-ui: group saved responses + past conversations at sidebar bottom
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>0583b1f -
chat-ui: move download saved responses below saved responses section
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>50485d1 -
chat-ui: pin past conversations to bottom of sidebar
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>30ef437 -
chat-ui: add subtitle prompting profession selection
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>52dd805 -
chat-ui: reduce starter pills to four
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>4c03494 -
chat-ui: widen starter pills to max-w-4xl for two-per-row layout
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>d22bdc6 -
chat-ui: constrain starter pills to max-w-2xl matching input width
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>ec67296 -
chat-ui: sidebar open/close toggle
webmuppet +1: Claude Sonnet 4.6Adds sidebarOpen state (default true) to App.tsx. A panel icon button in the top-left of the main content area toggles visibility. The sidebar wrapper is conditionally rendered based on that state, so the main area expands to fill the full width when closed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>7efd3df -
chat-ui: headline → 'Ask a marketing related question'
webmuppet +1: Claude Sonnet 4.6Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>63270bb -
chat-ui: hero input — larger textarea (4 rows, text-xl) matching Claude.ai scale
webmuppet +1: Claude Sonnet 4.6Split the shared formContent abstraction: the hero textarea now uses rows=4, text-xl (20px), leading-relaxed, and transparent background so it reads as a prominent focal point. The bottom variant retains rows=1 / text-sm compact bar. Hero submit button moves to a right-aligned row beneath the textarea with a text label ("Send") rather than icon-only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>0ab00bc -
chat-ui: centred hero layout with input as focal point, input moves to bottom on chat
webmuppet +1: Claude Sonnet 4.6Empty state: title + ChatInput (hero variant, floating card) + starter pills are stacked in a centred flex column occupying the full viewport height — matching the Claude.ai / ChatGPT pattern where the input is the primary call to action rather than a footer bar. Chat state: ChatThread takes flex-1 above, ChatInput (bottom variant, sticky footer) anchored below — same as before but now only rendered in this state. ChatInput gains a variant prop ("hero" | "bottom", default "bottom") so the two structural contexts can share all form logic (submit, keyboard, streaming disabled state) while rendering different container styles. All 66 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>c80e301 -
server.py: fix SSE streaming — run pipeline in thread to unblock event loop
webmuppet +1: Claude Sonnet 4.6The synchronous pipeline (run_query_retrieval + run_query_stream) was being called directly inside an async generator, which blocked the event loop for the full generation duration. FastAPI could not flush SSE token events to the client until the generator returned — all tokens arrived simultaneously at the end. Fix: run retrieval with asyncio.to_thread and bridge the sync streaming generator to the async context via an asyncio.Queue + daemon thread. The producer thread posts each chunk via loop.call_soon_threadsafe; the async consumer awaits each item and yields the SSE event, giving the event loop time to flush it to the browser between tokens. Verified: tokens now arrive ~0.4s apart matching the pipeline cadence, consistent with the Streamlit prototype's streaming behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8aca002 -
Refactor: replace Streamlit with React+Vite SPA + FastAPI server
webmuppet +1: Claude Sonnet 4.6Replaces app.py (Streamlit) with a React+Vite single-page application served as compiled static assets by a new FastAPI server (server.py). The Python backend (src/pipeline.py, retriever, generator, router) is untouched. All user-visible features are preserved: chat with streaming LLM responses, conversation history, saved responses, profession and language selectors, password gate, citation chips, Markdown export, and starter pill questions. Key changes: - server.py: FastAPI entry point — JWT auth, POST /api/query/stream SSE, static file serving of frontend/dist/ - frontend/: React+Vite+Tailwind SPA with full TypeScript, 66 passing tests including property-based tests (fast-check) for storage, export, citations, streaming, and fork isolation - Dockerfile: multi-stage build adding Node frontend compilation step; CMD switched from streamlit to uvicorn - pyproject.toml: streamlit/streamlit-js-eval removed; fastapi/uvicorn/ pyjwt added - CORS only enabled when ALLOWED_ORIGIN en…b5c0df4
-
Saved responses: store just the pair, not the full thread
webmuppet +1: Claude Opus 4.7 (1M context)Previously save_response captured st.session_state.messages[:i+1] — every message leading up to the saved Q&A. Clicking a saved response restored that entire thread into the chat, which contradicted the 'one save per question' mental model the user wanted. New shape: each saved record holds messages_pair (2 entries: user q, assistant a) and history_pair (the same exchange in the LLM-history shape). The single result dict sits flat on the record. Restoring loads just those two messages into a fresh conversation_id — new questions fork from that pair as the only context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>baa1903 -
Saved Responses: per-Q&A bookmarks with fork-on-click
webmuppet +1: Claude Opus 4.7 (1M context)Replaces the per-message .md download with an in-app save that bookmarks that Q&A pair into a new sidebar section. Clicking a saved response forks the conversation — restores the snapshot context under a fresh conversation_id, leaving the original conversation untouched. The sidebar now has three vocabularies: - Save (in-app, Q&A pair → sidebar) - Saved Responses (the section holding them) - Download saved responses (aggregate → single .md file) - Recent conversations (renamed from Past conversations) Save is idempotent on (conversation_id, msg_index) — re-clicking the same Q&A is a no-op; the button renders 'Saved ✓' (disabled) once saved. Storage lives in a separate localStorage key (hmc-saved-responses) so conversation schema changes don't risk corrupting bookmarks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>2260b35 -
Per-message Save: restore .md download on history + live render paths
webmuppet +1: Claude Opus 4.7 (1M context)Both paths had been left as no-op st.button() placeholders from the earlier disable/re-enable cycle — the sidebar "Save conversation file" was restored but these two weren't, so per-message Save chips did nothing on any message after the first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>a2fa334
-
Kebab menu icon: skip the bounding-rect path, target only the dots
webmuppet +1: Claude Opus 4.7 (1M context)Previous CSS used a wildcard '[data-testid=stMainMenuButton] *' that set fill on every descendant — including the Material SVG's '<path fill=none d=M0 0h24v24H0V0z>' bounding rect. The wildcard overrode fill=none, turning the bounding rect into a solid white square that hid the three dots underneath. Fix: ':not([fill=none])' guard on the path selector preserves the bounding rect's transparency while still colouring the dots path white. Verified locally — path 0 computed_fill stays 'none', path 1 gets rgb(235, 237, 247). Pattern worth reusing wherever we colour Streamlit's Material SVG icons — many of them ship with the bounding-rect-fill-none pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1846b25 -
Main menu (top-right kebab): light popover, dark text, violet hover
webmuppet +1: Claude Opus 4.7 (1M context)The kebab dropdown was rendering dark-on-dark via theme primitives — items unreadable. Inverted to a light system-menu treatment: white popover surface (#FFFFFF), 1px violet-tinted border, deep- ink text (#1C2942), violet-tinted hover wash that brightens the text to brand violet. Matches the chat-bubble 'lit floating panel' pattern. Targeted testids: stMainMenuPopover, stMainMenuList, stMainMenuItem, stMainMenuItemLabel, stMainMenuDivider. Plus the kebab trigger button itself (stMainMenuButton) keeps the light icon on the dark toolbar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>999dda3 -
Password gate: add explicit Log in button
webmuppet +1: Claude Opus 4.7 (1M context)Adds st.button('Log in', type='primary') below the password input. Calls the same _verify() function as the on_change handler, so both pressing Enter in the field AND clicking the button trigger verification. The button gives a clear affordance for users who don't reflex-submit with Enter. Inherits the existing .stButton > button violet-gradient styling automatically — no extra CSS needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>56b42fd -
Password input: kill the double-translucent composite
webmuppet +1: Claude Opus 4.7 (1M context)The 'darker patch behind eye' the user kept seeing was actually the opposite — the INPUT FIELD area was LIGHTER, not the button area darker. Cause: both the <input> element AND the [data-baseweb=input] wrapper had rgba(255,255,255,0.06) backgrounds, so the field area got a double translucent composite (~0.12 effective) while the eye button area (transparent button on the wrapper) had just the single 0.06 layer. Fix: make the <input> element fully transparent so only the wrapper provides the surface. Now the entire input field is uniform rgba(0.06) — no visible mismatch where the eye sits. This was a perceptual rather than a CSS-cascade issue — chasing the wrong target for several rounds. Verified via DOM sample at the eye location: ALL ancestors transparent except the wrapper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>98fed98 -
Restore Save conversation download as 'Save conversation file'
webmuppet +1: Claude Opus 4.7 (1M context)Re-enables the markdown export that was disabled earlier. Click the sidebar 'Save conversation file' button → instant browser download of a .md with the full conversation (frontmatter + each Q&A turn + sources). Filename derived from first user question. Browser-local persistence (the 'Past conversations' list) still handles save-and-resume automatically — this button is the export path for users who want a portable file to share or paste into their notes app. Existing helpers (format_full_conversation, make_filename) wired back up unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>06b840c
-
Past conversation titles: wrap to multi-line so full question is readable
webmuppet +1: Claude Opus 4.7 (1M context)Replaced the ellipsis-truncation pattern with multi-line wrap. Titles now show the full question across however many lines they need. Trade-offs vs ellipsis: - Loses uniform row height (rows grow with title length) + Reads at a glance — no need to hover or click to see full text CSS changes on past-conv buttons: - white-space: nowrap → normal - overflow: hidden → visible - text-overflow: ellipsis → clip - Added word-wrap/overflow-wrap: break-word for long unbroken strings safety - height: auto on button to grow with content - align-items: flex-start so the ✕ button sits at the top of the row when the title wraps to multiple lines - Slightly more padding (0.55rem vs 0.4rem) to give multi-line text some breathing room - line-height: 1.35 on inner content for readable wrap Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>0d151d8 -
Prompt: respond in English by default (mirror user's language otherwise)
webmuppet +1: Claude Opus 4.7 (1M context)Sonnet was occasionally answering in French despite an English-only prompt and English-only corpus — variance / glitch with no upstream cause. Adding an explicit instruction to default to English, with mirror-the-user-language as the explicit override path. Plus a defensive note: even if the model answers in another language, keep regulatory references (section numbers, Act names, quoted text) in their original English so the citations remain accurate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>96f8e03 -
User question bubble: h3-weight text + 20% white border
webmuppet +1: Claude Opus 4.7 (1M context)Two adjustments to the user question bubble: 1. Border: 1px solid #FFFFFF → 1px solid rgba(255, 255, 255, 0.2) The hard white keyline was visually loud against the violet panel; 20%-alpha white reads as a quieter rim. 2. Text: 1.375rem/700 (h2) → 1.15rem/600 (h3). The user's question was visually competing with the assistant's '## Short answer:' h2 in the response below — pulling it down to h3 weight keeps the conversation hierarchy readable: question (h3) sits below the assistant's section markers (h2) in visual prominence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>453a18e -
Past conv title ellipsis: target ALL descendants of button
webmuppet +1: Claude Opus 4.7 (1M context)The 5-level deep label DOM (button > div > span > div > p) had a SPAN with max-width:none in the middle that allowed the layout to balloon to the natural text width. Targeting the inner div and p alone wasn't enough — the SPAN's overflow was the choke point. Switched to '.stButton > button *' wildcard with min-width:0 + max-width:100% on every descendant. Verified ellipsis_active=true on a 70-char title via Chrome DevTools — p.scrollWidth=554 vs p.offsetWidth=181 means text is clipped and the '...' renders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>8a67528 -
Fix past-conversation delete (zombie bug) + title ellipsis
webmuppet +1: Claude Opus 4.7 (1M context)Two bugs fixed: 1. Delete didn't actually remove from localStorage. Root cause was the streamlit-js-eval st.rerun() race: calling st.rerun() inside the click handler aborted the script before the component was sent to the frontend, so the JS that writes to localStorage never executed. UI looked correct (cache mutation) but on browser refresh the deleted conversation came back from localStorage as a 'zombie'. Fix: deferred-write pattern. queue_delete() stashes the id on session_state, click handler reruns for UI, and the next render calls process_pending() at the top — which runs the actual delete + write inside a render that completes normally. The component sends to the frontend, JS executes, localStorage updates. 2. Past-conversation titles were hard-cut without ellipsis. text-overflow:ellipsis was on the button, but Streamlit wraps button labels in an inner div/p — the overflowing text node was the inner element, not the button. Moved …71d5674 -
Password eye: kill the transition that was overriding !important
webmuppet +1: Claude Opus 4.7 (1M context)Root cause was NOT cascade specificity — was a CSS transition on [data-baseweb='base-input'] from Streamlit's theme that kept interpolating from theme.secondaryBackgroundColor (rgb(21,19,31)) toward the resolved value. Static !important declarations can't beat an active transition because transitions are a higher cascade origin than author !important. Verified by inspecting Element.getAnimations() in DevTools — found a 200ms transition with start=rgb(21,19,31), end=<my target>. Even setting an inline 'background-color: hotpink !important' didn't change the computed bg until 'transition: none !important' was also added. Fix: 'transition: none !important' on [data-baseweb='base-input'] plus 'background: transparent !important'. Both required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>0721e1f -
Password eye: nuke every descendant background, swap hover to opacity
webmuppet +1: Claude Opus 4.7 (1M context)Wildcard rule: every element + ::before/::after pseudo inside the input wrapper gets background:transparent + background-color: transparent + background-image:none. Catches any BaseWeb slot, button shell, or pseudo-element that might still paint a patch behind the eye. The input wrapper itself ([data-baseweb='input']) keeps its translucent rgba surface — only its DESCENDANTS get nuked. Hover affordance switched from background-swap to opacity (0.85 → 1) so even on hover no background can come back. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>9e23589 -
Password eye: clear the BaseWeb enhancer-slot background
webmuppet +1: Claude Opus 4.7 (1M context)The button itself was transparent but the slot wrapper around it (BaseWeb's EndEnhancer container — the last direct div inside [data-baseweb='input']) was rendering with its own darker bg. Added transparent overrides on that wrapper so no patch shows behind the eye glyph. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>eaf7124 -
Password eye toggle: white via button selector (was missing the right testid)
webmuppet +1: Claude Opus 4.7 (1M context)The earlier fix targeted [data-testid='stTextInputIcon'] which is the help-tooltip icon, not the password show/hide eye toggle. The eye lives in BaseWeb's input enhancer slot as a plain <button> with no dedicated testid. Adding selectors for any button inside [data-testid='stTextInput'] or its [data-baseweb='input'] wrapper, with svg/* descendants forced to white. Subtle white-alpha hover background for affordance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>dd05b48 -
Docs: bring CLAUDE.md + README.md up to current state
webmuppet +1: Claude Opus 4.7 (1M context)CLAUDE.md was still describing the Phase-B-paused, pre-deploy, pre-restyle, pre-persistence state from months ago. README.md the same. Updates: - Status sections rewritten to reflect: corpus + indexes built and committed, HF Space deployed and password-gated, Instrument violet restyle shipped, browser-local persistence shipped, system prompt extracted to prompts/generator_system.md, generator structure tightened with Short answer / Sources / Other things to consider scaffold. - Repo layout updated: prompts/, src/persistence.py, Dockerfile, requirements.txt, STRATEGY.md (gitignored), updated scripts/ list, updated docs/ list. - Conventions section: Python 3.13 (was 3.14), dependency split (base / [build] extra), system-prompt-as-markdown convention, CSS targeting strategy, deployment section, browser-persistence section with the apostrophe-escaping bug callout. - Don'ts: removed stale 'don't run make index until Phase B complete' (Phase B is complete), added deplo…74119d4 -
Revert: response bubble back to solid #FFFFFF
webmuppetb82cdb2 -
Response bubble background: rgba(255, 255, 255, 0.8) — 80% white
webmuppet +1: Claude Opus 4.7 (1M context)Lets the violet canvas tint show subtly through the response card, so it reads as floating on the gradient rather than punching out a hard white block. User bubble overrides to solid violet via :has() so this change only affects assistant messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>31403de -
User question bubble: keyline 2px → 1px
webmuppetd72e0d4 -
User question bubble: 2px white keyline
webmuppet4a987a6 -
User question bubble: violet bg (#4023A8) + white h2 text
webmuppet +1: Claude Opus 4.7 (1M context)Matches the chat input panel's violet — same medium-violet surface with white text. The user's question now reads as a clear violet 'spoken' card right-aligned, paired visually with the input panel at the bottom of the same colour. Border switched to transparent (was lavender alpha) since it's no longer a light card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>77d21d4 -
Merge refine/chat-bubbles into main
webmuppet +1: Claude Opus 4.7 (1M context)Chat bubble refinements: - Heading hierarchy: h1/h2 violet bold, h3 deep ink semibold, h4-h6 muted dark medium. Restores visual hierarchy beyond just size. - User question rendered as h2 (violet bold 1.375rem) inside the user bubble — reads as a conversation heading. - User bubble narrowed to 75% max-width, right-aligned, avatar hidden — modern chat-app convention. - Adjacent-sibling combinator tightens h2→h3 stacked spacing. - Sidebar profession label renamed: 'Select your profession' → 'What do you do'. - First profession option renamed: 'General Advice' → 'Select Profession' with defensive session_state migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1e294d2 -
Sidebar profession label: 'Select your profession' → 'What do you do'
webmuppetb4b0514