Spaces:
Runtime error
Runtime error
Commit History
-
Feat: capture inbox (Phase B) β phone captures merge into the libraryβ
/capture with a token enqueues place(s) into an ephemeral, TTL'd in-memory inbox. New POST /capture-inbox/{token} drains a token's captures and merges them into the caller's library (reuses the living-library _merge_rows) β returns a fresh job like /import. Client _pullCaptureInbox() drains on load and shows "π² N places captured from your phone" with β¨ New badges; a #capture-setup card in the import card surfaces the token (localStorage). Fixes an empty-multipart-body 400 by always sending merge_into (even ""). tests/test_capture.py extended (enqueue, drain+merge, idempotency, status preserved); visual review of the setup card + full round-trip at mobile/desktop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>eda6c91 -
Feat: /capture endpoint core (Phase A) β single-post extractionβ
Refactors _run_url_pipeline's extract core into shared _places_from_caption() (analyze_batch β split_multi_venue β location-tag coords). New POST /capture accepts embed_html (the iOS Shortcut fetches /embed/captioned/ on-device so the server never touches Instagram), or a ready caption, or a url (local fetch), plus optional lat/lng for a free location-tag pin. Returns {places, count}. BYOK api_key. tests/test_capture.py added to CI; e2e still green (refactor safe). Inbox delivery (Phase B) and the Shortcut itself (Phase C) are next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>8221e29 -
Feat: living-library merge polish (Phase 3)β
- /import returns added_urls + kept; merge_rows now returns the added rows. - Client tags freshly-merged places (row._isNew) β a "β¨ New" card badge, floats them to the top of Browse (stable, transient β resets on reload). - Merge banner reports "N new Β· M already in your library". - Import card gains a Merge/Replace radio (#import-mode), shown only when a cached library exists; a zero-overlap merge still offers Replace inline. Completes the living-library feature (Phases 1β3). Visual review at mobile + desktop; tests/test_merge.py extended (added_urls/kept) and green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>f981d71 -
Feat: living-library cost-skip on re-extract (Phase 2)β
POST /upload now accepts merge_into (the cached library CSV). _run_pipeline seeds it as the job's places_full.csv and runs extract.run(resume=True), so _warm_start_from_csv skips the LLM on posts already in the library (keyed on instagram_url). new_urls excludes known posts, so the cost estimate and progress message reflect only genuinely new ones ("X already in your library, skipped"). After extraction the result is folded back with _merge_rows (keep-existing-wins) so known posts keep status/coords and any place the new export omitted is re-added. Client sends the cached library on the Extract upload. tests/test_merge.py gains a warm-start section that patches extract.analyze_batch to prove known posts never reach the LLM and keep their visited status. A 600-post re-export where 550 are already mapped now only pays to extract the 50 new ones. Phase 3 (polish) is next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>1a26356 -
Feat: living-library merge on import (Phase 1)β
POST /import now accepts an optional merge_into field (the caller's existing library CSV). The uploaded file is folded into it server-side via _merge_rows, keyed on instagram_url (else normalized name|city), keep-existing-wins β so user-owned status and manual edits are never overwritten and only genuinely new posts are added. Response gains added/merged/no_overlap. Client (importPlacesFile) sends the cached localStorage library as merge_into (unless restoring, which forceReplaces), shows an "N new places added" banner, and offers Replace when nothing in the file matched the library. Also fixes the restore card lingering after a library loads (showSubTabBar now hides it). This is the linchpin of the hybrid plan β it's what lets a capture/Shortcut or a re-import grow one living map instead of replacing it. Pinned by tests/test_merge.py (added to CI guardrails). Phase 2 (server-side warm-start cost-skip for bulk re-export) is next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.cβ¦d1b556d -
Docs: FAQ entry for local-Docker-only features; TODO single-URL local doneβ
Add an FAQ item listing the features that only work on the local Docker build (single post URL, Ollama, server-side geocoding) and why hosted can't do them. Update the TODO single-URL item to reflect the local path is working/confirmed (urllib embed + curl_cffi backup), leaving only the hosted half β which has no free path (IP wall + no CORS; native-app share-sheet is how competitors do it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>d302d2e -
Feat: flush pending library cache on tab background/closeβ
Adds flushCache() wired to visibilitychangeβhidden + pagehide so a status/ field/coord edit made within the ~1.5s debounce window is persisted to localStorage before the user leaves, instead of being lost. Also prunes completed items from TODO.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>90d15a0 -
Feat: client-side library persistence (localStorage restore card)β
Cache the canonical CSV in the browser after load and write-through on every edit (status, fields, coords). A returning visitor with no live job sees a #restore-card that replays the cached CSV through /import β a pure reconstruction-cache, so status/coords/edits round-trip losslessly with no new data shape and no server change. Stored only on-device; Download stays the durable backup. Works in both deployment modes (no HOSTED branch). - app.js: importPlacesFile() refactored out of handleImportFile; cacheLibrary/ cacheLibrarySoon/maybeShowRestore + restore/forget handlers; quota note - index.html + app.css: #restore-card above the import card - tests/test_persistence.py: cache/restore round-trip is lossless (CI guardrail) - docs: README, CLAUDE.md, TODO (item marked done; IndexedDB deferred) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>1c43cdb
-
Docs: split Instagram export guide into desktop + mobile stepsβ
Harshith Belagur +1: ClaudeThe "How to get your Instagram file" guide, FAQ entry, and README only documented one entry path (phrased for the app). Split each into a π» computer (instagram.com) flow and a π± Instagram app flow, sharing the common Accounts Center β Download your information steps. Adds an .export-platform heading style for the two labeled blocks in the Extract tab. Co-Authored-By: Claude <noreply@anthropic.com>08d1d42 -
Fix: fast-fail on hosted URL fetch with clear error messageβ
Instagram blocks all cloud server IPs (HF, CF Workers β returns login wall). URL feature is local Docker only. Hosted builds now immediately return a clear error instead of waiting 30s for yt-dlp to time out. CF Worker script kept in deploy/ for future reference but unused. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>54d09f0 -
Fix: URL fetch works on hosted via Instagram's embed/captioned endpointβ
Uses /embed/captioned/ (statically served for website embeds β works from any server IP, no proxy needed) as the primary caption source. The proxy path (CAPTION_PROXY_URL) is kept as an override but is no longer needed or set by default. yt-dlp remains as a local fallback that also captures geotag data. Adds 3 new unit tests for _parse_embed_caption (basic, empty page, creator fallback) bringing pipeline checks to 101. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>495ba69 -
Fix: Sonnet default, thinking-block skip, multi-venue URL extractionβ
- DEFAULT_MODEL: claude-fable-5 β claude-sonnet-4-6 - extract.py: skip thinking blocks when reading LLM response (claude-fable-5 and extended-thinking models prepend a thinking block; content[0].text is None β now finds first text-type block) - extract.py: max_tokens floor of 1024 so single-post calls aren't truncated by the 250-token default - extract.py: system prompt now treats venue @handles as strong signal that a post IS about a place; multi-venue handles prompt the model to return an array - extract.py: _parse_batch_response no longer trims multi-venue arrays for single-post calls (expected=1) - web/app.py: _run_url_pipeline uses analyze_batch directly so all venues from a multi-handle post are captured Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>256d2ea -
Feat: single Instagram URL extraction via CAPTION_PROXY_URLβ
Re-adds the URL input on the Extract tab. Caption fetch now routes through a configurable proxy (CAPTION_PROXY_URL) on hosted builds β default allorigins.win requires no account. Local Docker skips the proxy and uses yt-dlp + direct OG scraping instead. pipeline/transcribe.py: fetch_post_metadata gains proxy_url param; _fetch_via_proxy handles allorigins.win JSON response format. web/app.py: _run_url_pipeline + /extract-url + /url-chatbot-prepare restored; reads CAPTION_PROXY_URL from env. deploy/hosted.env.example: CAPTION_PROXY_URL=https://api.allorigins.win/get TODO.md: P0 item for server-side video transcription; URL item updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>56b50ce -
Revert: remove single Instagram post URL featureβ
Removed the URL input from the Extract tab, all backend routes (/extract-url, /url-chatbot-prepare), JS logic, CSS, and the fetch_post_metadata/OG scraping helpers from transcribe.py. Root cause: yt-dlp needs --cookies-from-browser for Instagram auth (works locally, not on a hosted server). Tracked in TODO.md as a potential local-only CLI flag (--url) instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>425566e -
Fix: API key gate for URL mode; faster timeouts; better network error messageβ
Three issues addressed: 1. "Extract now β" was enabled without an API key on hosted deploys. Added data-has-server-key body attr; _syncUrlModeButtons() now disables the button with "Enter API key above to continue" when Anthropic is selected and neither a server key nor a typed key is present. Re-checks on every keystroke in the API key field. 2. Both yt-dlp and OG scraping were timing out on HF Spaces (Instagram is likely blocked from HF's network). Reduced timeouts: yt-dlp 15s, OG HTTP 8s, so failures surface quickly rather than hanging for 30s+. OG scrape tries two SSL contexts before giving up. 3. When both fetches fail, the error now says "try the free chatbot mode instead" since that path only needs the caption pasted by the user. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>0c8fe05 -
Feature: chatbot (free) mode works for single URL extractionβ
Fetches the post caption server-side (yt-dlp / OG scraping, no API key), wraps it as a single-post chatbot export, and hands off to the existing chatbot-step2 UI β same download JSON + copy prompt + paste response flow as the full-export chatbot path. - POST /url-chatbot-prepare: fetch caption β chatbot_mod.prepare_export([post]) - app.js: chatbot+URL shows "Prepare extraction file β"; clicking it calls /url-chatbot-prepare and populates chatbot-step2 without touching the existing file-based chatbot flow - _syncUrlModeButtons() centralises run-btn vs chatbot-prepare-btn visibility for URL mode so setProvider() stays clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>42e5a24 -
Fix: add yt-dlp to web requirements; block chatbot+URL modeβ
Two HF Spaces bugs: 1. yt-dlp was commented-out/optional β not installed in the Docker image. Added to requirements-web.txt so it's always present on web deploys. 2. Entering a URL while chatbot provider was active hid the prepare button, showed run-btn, then silently submitted as anthropic without a key and failed. Now URL mode blocks run-btn with an explanatory message when chatbot is selected, and setProvider() respects URL mode state when the provider is changed after a URL has been typed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>47dfeed -
Feature: extract a single place from an Instagram post URLβ
Users can now paste any https://www.instagram.com/p/β¦ URL directly in the upload card as an alternative to the full export ZIP. yt-dlp fetches the post caption and optional geotag server-side, the same LLM extraction + geocode + KML export pipeline runs on the result, and progress is streamed via the existing SSE flow β no new UI states needed. - pipeline/transcribe.py: fetch_post_metadata() uses yt-dlp --dump-json - web/app.py: _run_url_pipeline() + POST /extract-url route - index.html: URL input field + "or paste a single post URL" divider - app.js: URL mode β typing a valid IG URL enables the run button, clears any pending file; selecting a file exits URL mode - app.css: .url-or-divider and .url-input-row styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>714e84b
-
UX: footer copy, mobile hero stack, visual review workflowβ
- Footer: "Open source β star it, fork it, break it, fix it, make it yours. Contribute on GitHub β" - Hero stacks vertically on mobile (β€480px): full-width Watch demo button below copy - CLAUDE.md: document required visual review (Playwright 390/768/1280px) before every front-end commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>bc8eb05 -
UX: update footer copy to dev-friendly OSS calloutβ
"Open source β star it, fork it, break it, fix it, make it yours. Contribute on GitHub β" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>bca3ed3 -
Fix: footer centering and visited link colorβ
Add max-width + margin:auto so footer aligns with card content, not the page edge. Add a:visited rule to prevent browser-default purple link color. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>311b263 -
UX: remove docker note from hero, add OSS footerβ
- Hero card is back to clean copy + button only; Docker info lives in the FAQ where it belongs - Add #site-footer: "Open source β contribute on GitHub β", border-top, muted text, full-width Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>fada9fe -
Fix: demo hero layout β nested structure fixes copy collapse and docker note rowβ
Wraps copy+button in .demo-hero-main (flex row) inside #demo-hero (flex column) so the docker note reliably sits below as its own full-width row. Previously all three siblings were in a flat flex row, causing the copy to collapse into a narrow word-wrap column. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8822e0c -
UX: fix demo hero font sizes and docker note copyβ
- Remove hardcoded 0.85rem from strong (inherits body default), bump span to 0.88rem and docker note to 0.82rem so the hero reads consistently with the rest of the UI - Reword docker strip to "Want to run it locally? Docker version on GitHub" β avoids implying only Docker is free Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>bdaa9fe -
UX: Docker nudge on landing, FAQ item, Ollama-aware tour stepβ
- Add `.demo-docker-note` strip inside `#demo-hero` linking to GitHub repo for self-hosting with Docker - Add FAQ item "Can I run this fully offline and free with Docker?" (after AI models FAQ) - Tour model-picker step (`model-card`) is now Ollama-aware: on hosted (`OLLAMA_ENABLED=false`) the Ollama option is omitted; `body` can be string or function - Update README.md and CLAUDE.md to document all three changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>7d20eac -
Fix: Escape key closes the demo tourβ
Wire keydown Escape to endTour() so users can exit at any step (previously only the β button and overlay-click worked; Escape only closed the feedback modal). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>44d956c -
UX: floating feature-request button β pre-filled GitHub issueβ
- Floating π‘ pill button (bottom-right, z-index 9000, safe-area-inset aware for iOS) - Click opens a modal (title + details fields, slide-in animation, Escape / overlay-click to dismiss) - Submit constructs issues/new?labels=enhancement&title=β¦&body=β¦ and opens in new tab - Collapses to emoji-only below 480 px so it doesn't crowd mobile screens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>9f1919e -
UX: demo tour with dummy data, hero placement on landing pageβ
- tour.js: inject 8 real-world demo places (Tokyo/London/Paris/LA/Bangkokβ¦) into allRows so users see a fully populated Roulette pick, Browse grid with Leaflet map, and download bar β no upload needed - HTML: move tour trigger from FAQ tab to a #demo-hero card between the header and the nav (βΆ Watch demo) - app.css: replace .tour-cta-card with #demo-hero styles; tour state is fully restored on exit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>3e19b3d -
UX: add guided tour, pipeline timing noteβ
- tour.js: 6-step spotlight tour (upload β model β run β reimport β roulette β pipeline diagram); simulates tab navigation and temporarily reveals sub-tab-bar in demo mode; triggered by βΆ Take the tour button in FAQ - Progress card: static timing note ("usually 5β10 min β may appear stuck") so users don't abandon a running job - app.css: tour overlay/spotlight/tooltip styles + .progress-timing-note + .tour-cta-card/.btn-tour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>5c44de2 -
UX: add FAQ tab with pipeline diagram, About, and accordion FAQβ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>23dabb1 -
Rename: Food Mapper β IG Food Mapperβ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>a9cca6c -
Rename: Places Mapper β Food Mapperβ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>a0800ee -
UX: remove quality scores from model cardsβ
Scores were mostly dashes (β) for newer models and added noise rather than signal. Cards now show only the cost estimate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>2a30ad3 -
UX: stop auto-opening export guide when drop zone is clickedβ
The guide was popping open every time a user clicked the drop zone to upload a file, which was disruptive. It stays closed unless the user explicitly opens it via the <details> toggle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8e8ea27 -
UX: run button says 'Extract now β' once a valid file is chosenβ
Previously stayed 'Choose a file to continue' even after a file was successfully validated, which was confusing after entering an API key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>6fe5d5b -
Fix KML round-trip: embed City/State in description CDATA; read them back on importβ
Previously the KML export stored city only in the folder hierarchy (country β city β placemark), but the actual export uses flat country-only folders β so city and state were silently dropped on re-import, leaving all rows with city=UNKNOWN. - export.py: write "City: {city}" and "State: {state}" lines into the Placemark description CDATA so they survive re-import regardless of folder structure - web/app.py _parse_import_kml: parse "City:" and "State:" from CDATA; CDATA values take priority, folder-derived city used as fallback - test_pipeline.py: update KML fixture to flat country folder + CDATA city, add explicit city/state CDATA checks, add folder-city fallback test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8a1e3cd -
Fix: roulette location search matches city OR country; strip UNKNOWN from displayβ
- /roulette city filter now matches r.city OR r.country (case-insensitive), so typing "United States" or "Japan" returns places from that country - Roulette datalist populated with both city names and country names so autocomplete suggestions appear for either - Label changed from "City trip:" to "Location:" with placeholder "city or country" - pick-loc display filters out "UNKNOWN" values (same guard already applied to category/cuisine/price in metaParts) - TODO.md: document confirmed KML city-level hierarchy breakage in Google My Maps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>a2095dc -
Fix: hide roulette panel on initial Explore load (no data yet)β
Panel had class="sub-panel active" in HTML, causing it to appear immediately when the Explore tab was clicked before any file was loaded. sub-panels should only become visible after showSubTabBar() fires. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>9b8d87e -
Fix: remove #roulette-section { display:none } β roulette panel was blankβ
Leftover CSS rule permanently hid the roulette card content regardless of the sub-panel active state. Visibility is handled by .sub-panel.active now. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>ccd6e8c -
UX: drop Exports tab, auto-spin Roulette on first visitβ
- Remove the Exports sub-tab entirely β it was a one-shot action poorly placed as a dedicated tab. Download buttons (KML / GeoJSON / CSV) and the Google My Maps instructions now live in a #download-bar at the top of Browse, revealed when showSubTabBar() fires. - Roulette now auto-spins on first visit: showSubTab('roulette') triggers spin-btn.click() if #pick-card is still hidden. Subsequent tab switches don't re-spin (card is already visible). - Clean up orphaned success-block / success-ring CSS (no longer used). - Update test_routes.py: assert no exports tab, assert download-bar is inside the explore panel. - CLAUDE.md + README.md updated to reflect two-sub-tab nav. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>36362ca
-
Web UI: two-tier nav β Extract / Explore tabs visible from landingβ
Top nav (always sticky): [π₯ Extract] [πΊ Explore] - Extract tab: full pipeline UI (upload, model picker, run, progress) - Explore tab: KML/CSV import card; once data loads a sub-tab bar appears with [β¬ Exports] [π² Roulette] [π Browse] After extraction β auto-jump to Explore β Roulette (nudge toast). After KML/CSV import β Explore β Browse. οΌ New button switches back to Extract tab (no page reload). Clean up TASKS.md (remove completed phases) and TODO.md (remove all completed items, keep only open work). All 24 e2e + 14 structural + 2 guardrail tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>383680a -
Revert session restore: import card is the right returning-user flowβ
The server-ping restore banner was over-engineered β job data expires after 24h anyway and returning users naturally drop their KML/CSV into the always- visible import card to get straight to Roulette/Browse. Removed _restoreSession IIFE, #restore-banner HTML, and all associated CSS/JS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>519bd6d -
Web UI: session restore for returning users + food-only filterβ
Returning users: showTabBar() now persists lastJobId to localStorage. On page load, _restoreSession() pings /results/{id}; if data is alive a #restore-banner offers Resume / Start fresh β no re-upload needed. Food-only filter: FOOD_CATEGORIES (Restaurant, Bar, Cafe, Bakery, Market) defined in both app.js and web/app.py. filterState.foodOnly defaults ON (persisted in localStorage). applyFilters() gates the Browse grid + map; /roulette accepts food_only=true and filters server-side. Toggle chip #food-toggle in the filter bar; active state kept in sync by applyFilters(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>facc20d -
Web UI: Phase 4 tab-bar UX polishβ
- Tab badges: gradient pill (ig1βig3), hidden via CSS :empty + explicit display:none when count is 0 so empty pills never render - Stat strip: shows ~$X.XX cost alongside places/pinned when non-zero - Mobile: -webkit-overflow-scrolling:touch on .tab-bar-inner for iOS momentum scroll Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>89f8c84 -
Web UI: tab-bar SPA, KML/CSV import, refactor index.htmlβ
Three-tab SPA (Exports | Roulette | Browse) replaces the post-extraction long scroll. Roulette is the default tab after extraction (with nudge toast); Browse is the default after import. Tab badges show unvisited/total counts. KML/CSV import card (always visible before extraction) lets returning users skip re-extraction entirely β POST /import parses places_map.kml or places_full.csv and returns a job immediately at step "done". Refactored index.html: CSS β web/static/app.css, JS β web/static/app.js. Template drops from 2553 β 465 lines; Jinja vars passed via body data-* attributes. FastAPI serves static/ via StaticFiles mount. All 24 e2e tests pass; deployment-mode and prefilter guardrails unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>74aafdd -
Chatbot UI: style the response-file picker on-brandβ
Replace the browser-default "Choose Files" control with a hidden native input + an on-brand .btn-outline label ("π Choose response file(s)") and a filename display that confirms what was added (β names / β empty). Cleared on reset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>79575f0 -
Chatbot UI: upload the reply as a file (too long to copy-paste)β
Add a file input next to the paste box: reads each selected .json/.txt with FileReader and feeds the existing _chatbotResponses flow (supports multiple files for chunked libraries). Works whether the chatbot generated a file or the user saved its reply as text β no backend change (/chatbot-process already accepts the response as text). Resets with the rest of the chatbot step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0ccd10d -
Fix: index 500 on HF β use new Starlette TemplateResponse signatureβ
The Space returned Internal Server Error on every page: recent Starlette (fresh- installed in the container) removed the old TemplateResponse(name, {ctx}) form, which 500s with "unhashable type: dict". Switch to TemplateResponse(request, name, {ctx}) (request first), which works on both the local pin and the Space. Verified with a real TestClient GET / (200). The #16 check missed this because it rendered the template directly instead of hitting the route. CLAUDE.md: record the signature requirement as a convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>5e4388b -
#19: public-URL hardeningβ
For a public Space: MAX_UPLOAD_MB (25) rejects oversized uploads on /upload and /chatbot-prepare (raw + post-unzip, guarding zip-bombs); MAX_POSTS (5000) caps library size with a "use Docker/CLI locally" message; safe_err() redacts sk-ant-β¦ keys from error messages/SSE. Guardrail test covers safe_err; CLAUDE.md env list updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>8ec40f3 -
#18: extend guardrail test for client-geocode + set-coordsβ
Extract valid_latlng() from /set-coords (pure) and add a client-geocode section to tests/test_deployment_modes.py: client-geocode ON when the server is bulk-blocked (hosted) / OFF for local Docker, and valid_latlng accepts valid coords while rejecting non-numeric and out-of-range. Runs in the guardrails CI job. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>50e43d1