Commit Graph
190 Commits
Author SHA1 Message Date
DorianandClaude Opus 5 e30ac1d106 feat(app): delegate chat to Archy's node-side assistant loop when embedded (Archy phase 13, D-01/D-17)
Adds archyBridge.sendChat(text) built on the existing postToParent +
origin-validated listener pattern (same request-id correlation as
requestContext), with a 180s timeout matching the node's
ASSISTANT_HTTP_TIMEOUT. Adds useAI.ts's streamViaArchy, which branches all
three existing send sites on the same __AIUI_EMBEDDED__ signal useArchy.ts
already reads: embedded mode delegates the model call, the tool-calling
loop and the model key to the node; standalone mode is untouched and keeps
using streamClaude/streamOpenRouter with AIUI's own dev proxy (D-17).

CLAUDE_PATH/OPENROUTER_PATH are not removed — 13-02 changes what those
paths resolve to on a node, 13-09 retires them.

Verified: vitest run 332/335 passing (3 pre-existing failures confirmed via
a scratch worktree at the prior HEAD, unrelated to this change — seed
extraction count assertions and a web-search-integration body.webSearch
assertion); vue-tsc --noEmit clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 14:38:19 -04:00
DorianandClaude Fable 5 6e8b96d135 fix(app): embed round-trip fixes — origin, dark bg, key fallback, CLI path
Four issues found via live testing of the embedded Chat/AIUI panel
(Archipelago phase 02-07 follow-up), each root-caused rather than
patched over:

1. Loading overlay never dismissed: archyBridge.init() used
   window.location.origin (this iframe's OWN origin) as the target for
   postMessage calls TO the parent, instead of the parent's actual
   origin. Silently correct only when AIUI is served same-origin as
   its host (production's /aiui/ proxy) — broken the moment AIUI runs
   on a different origin than its embedding page (any dev setup with a
   separate AIUI dev server). The 'ready' message, and every
   permissions/theme/context/action response after it, was being
   dropped by the browser. Fixed by deriving the parent's real origin
   from document.referrer (archyBridge.ts).

2. White/black background instead of the branded look: initTheme()
   decides light/dark from localStorage or the OS's prefers-color-
   scheme, with no awareness of being embedded — App.vue now forces
   dark immediately on mount when embedded (before any handshake
   completes) and useArchy.ts's theme-update callback now applies
   Archy's reported mode too. Separately, body had no background-color
   at all, so ChatPage.vue's embedded `background: transparent` fell
   through to the browser's white UA default; main.css now paints body
   to match the active theme. And ChatPage.vue's embedded branch was
   opting out of the same background-image treatment the standalone
   dark app uses — it now shares that exact styling instead of a flat
   fallback color, matching the standalone look precisely.

3. Dead end when no AI provider credential is available: useAI.ts now
   emits a narrow, one-shot needsApiKey signal (401/403, "api key",
   "unauthorized", or a proxy-unreachable failure — deliberately not
   every transient error) that ChatWindow.vue watches to auto-open
   Settings, so the user lands on the fix instead of a silent/dead
   chat.

4. claude-proxy.ts's CLI fallback spawned a hardcoded ~/.local/bin/claude
   path, breaking with ENOENT on any machine where the CLI lives
   elsewhere (e.g. an nvm install). Now resolves via `command -v claude`
   first (an optional CLAUDE_BIN env override, then the historical path,
   then the bare command name as a last resort so spawn() itself can
   still try PATH), and surfaces an actionable in-UI error naming three
   ways to fix it when none resolve.

Verified: full send→spawn→response round trip against the local proxy
(both directly and through vite's /api/claude proxy), vue-tsc clean,
vitest 332/335 passing (3 pre-existing unrelated failures, confirmed
present before this commit too), production build clean with both
chatExpanded/mobileChat flags and the new background rule present in
the built assets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 20:04:45 -04:00
DorianandClaude Fable 5 900c0b9060 feat(app): honor Archipelago D-14 embed defaults via query params
Archipelago's Chat tab (neode-ui) embeds AIUI in an iframe and now
passes two presentation-only query params on the embed URL:

- ?chatExpanded — chat starts in the full message list rather than
  the collapsed prompt-index. chatCollapsed's initial ref now checks
  for this param before falling back to the existing localStorage
  default; never written back to localStorage, so the standalone
  app's own persisted preference is untouched.
- ?mobileChat — on mobile, ChatPage opens on the chat tab rather than
  whatever tab survived from a prior mount of the module-singleton
  content-panel state. mobileTab already defaulted to 'chat'; this
  just re-asserts it once on mount when the param is present, so it
  doesn't interfere with the hasDetailOpen/panelOpen watchers driving
  normal tab switching in response to user taps afterward.

Both params are read directly from window.location.search and are
no-ops when absent, so the standalone (non-embedded) app and its
existing desktop layout are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:17:32 -04:00
DorianandClaude Opus 4.6 cb22131909 feat(app): apps tab, archy mock data, prompt palette, code browser, detail keys, guide routing
- Add [[app_ext:...]] tag format and rewrite extractApps() for reliable app extraction
- Wire AppsGrid and RecipeGrid into ContentGridView (was missing on wide desktop)
- Add mock Archy node data for standalone dev testing (VITE_MOCK_ARCHY=true)
- Fix PromptPalette: z-50 + opaque bg so slash menu renders above chat content
- Fix detail banner not updating: add :key to all detail components in ContentPanel
- Guide page moved to /guide, chat is now root route, guide auto-selected on first load
- Code browser: click opens file in viewer, separate checkbox for chat context selection
- Restore folder context selector (round checkbox on hover) in FileTreeNode
- Demo projects for prod deployment instead of hardcoded personal paths
- Improve Archy context injection with media breakdown and better error logging
- Add 11 Claude Code skills for efficient development workflows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:39:41 +00:00
DorianandClaude Opus 4.6 cc875d1c43 fix(app): update haiku model ID from retired claude-3-5-haiku to claude-haiku-4-5
The old claude-3-5-haiku-20241022 model ID returns 404 from the Anthropic API.
Updated proxy mapping and test to use claude-haiku-4-5-20251001.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:45:48 +00:00
DorianandClaude Opus 4.6 dc4c80a08b feat(app): add recipe content panel with grid and detail views
- Add RecipeGrid with search, meta badges (time/servings/calories), ingredient preview
- Add RecipeDetail with ingredient checklist, scaling slider, numbered steps
- Add generateRecipeFallback SVG for recipe cards (text-only style)
- Wire recipe extraction into content panel tab system
- Add isRecipeQuery/isRecipeLikeResponse classifiers in contentFiltering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:27:20 +00:00
DorianandClaude Opus 4.6 2304e5904e fix(app): fix image fallbacks across all content grids, refactor into useContentImages
- Create useContentImages<T> composable eliminating ~240 lines of duplicated
  image loading/fallback logic across 6 grid components
- Fix book covers: use full fetchBookImage chain (Open Library → Google Books → Wikipedia)
- Fix TV series images: try disambiguated Wikipedia title first (e.g. "Chernobyl (TV series)")
- Add Wikipedia image fetching for places (fetchPlaceImage)
- Rewrite all SVG fallbacks to consistent text-only style (no icons)
- Add generateWebsiteFallback for NewsGrid websites variant
- Fix song extraction regex catching raw song_ext: prefix in titles
- Fix player bar: clean song_ext: prefix from display, mute error text
- Fix Code panel: auto-load projects on mount when list is empty
- Improve chat bubble spacing (py-2.5) and first message top margin (pt-6)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:55:40 +00:00
DorianandClaude Opus 4.6 1eaf30ae12 fix(app): fix broken dev server after security hardening
- Fix dev.sh unbound variable crash with ${VITE_DEV_API_TOKEN:-}
- Kill stale proxy on startup instead of skipping (token mismatch)
- Fix RSS middleware blocking all GET requests (check path before auth)
- Read dev auth token lazily from process.env (not cached at import)
- Restore network binding (host: true) for Vite dev server
- Add macOS keychain lookup for Claude Code OAuth token in proxy
- Rewrite proxy streaming to pipe SSE directly instead of await json()
- Prevent double web search (client-side + proxy) in useAI
- Reduce SearXNG timeout 6s→3s and max tries 8→3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 03:09:06 +00:00
DorianandClaude Opus 4.6 e97c8f36ac test: update tests for security fixes (origin validation, streaming state)
- archyIntegration: expect window.location.origin instead of '*' for
  postMessage calls (matches FIX-009 origin restriction)
- useAI: fix flaky isStreaming assertion to account for background
  fetch calls (refreshWavlakeCatalog) captured before streaming starts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:42:50 +00:00
DorianandClaude Opus 4.6 9adeab9420 fix(app): validate content pack URL scheme and schema in importFromUrl
Require https: protocol for remote content pack imports, rejecting
http:, file:, javascript:, and other schemes. Add schema validation
to verify required fields (id, name, items) and item shape (type,
title) before accepting imported packs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:39:26 +00:00
DorianandClaude Opus 4.6 fa3c446baf fix(app): add 1MB body size limit to Claude proxy
Track accumulated body size during req.on('data') and abort with 413
if it exceeds 1MB, preventing unbounded memory allocation from
oversized payloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:38:39 +00:00
DorianandClaude Opus 4.6 6d6408e2a4 fix(app): bind Vite dev server to localhost only
Remove --host flag from dev.sh that was overriding vite.config.ts to
bind on 0.0.0.0. Server now defaults to localhost; use VITE_HOST env
var to opt-in to LAN access for mobile testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:38:01 +00:00
DorianandClaude Opus 4.6 6720711302 fix(app): remove plaintext fallback from key vault when crypto unavailable
storeApiKey() now throws when encryption is not available instead of
storing keys in plaintext. ApiKeyManager.vue catches the error and
displays a warning message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:35:48 +00:00
DorianandClaude Opus 4.6 5c4afe00e5 fix(app): add rate limiting to all API endpoints
Add sliding-window rate limiter in server/dev-auth.ts (60 req/min reads,
10 req/min writes per IP). Apply checkRateLimit() in all Vite plugins
and claude-proxy.ts after auth validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:34:41 +00:00
DorianandClaude Opus 4.6 6425b2f53b fix(app): restrict postMessage origin in archyBridge
Change default allowedOrigin from '*' to null. Derive from
window.location.origin when init() is called without explicit origin.
Always validate event.origin — reject messages when origin is not set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:31:45 +00:00
DorianandClaude Opus 4.6 4053eb46af fix(app): replace custom HTML sanitizer with DOMPurify
Install dompurify and replace the hand-rolled DOM walker sanitizer
with DOMPurify.sanitize() configured with the same allowed tags.
Handles mutation XSS edge cases the custom version couldn't.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:30:58 +00:00
DorianandClaude Opus 4.6 f7f8c140c3 fix(app): store NWC wallet secret in encrypted vault instead of plaintext localStorage
Replace localStorage.getItem/setItem with storeApiKey/getApiKey/deleteApiKey
from key-vault. Make loadConnection(), connect(), and disconnect() async.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:28:29 +00:00
DorianandClaude Opus 4.6 c3e58fdab8 fix(app): add body size limit to dev-chats write endpoint
Track accumulated body length during PUT /api/dev-chats and abort
with 413 if payload exceeds 5MB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:27:35 +00:00
DorianandClaude Opus 4.6 956e98b041 fix(app): add SSRF protection to RSS fetcher
Add post-DNS SSRF validation using dns.lookup() to verify resolved IPs
are not in private ranges. Block non-http(s) schemes (file://, ftp://)
in discoverFeedUrl(). Extract isPrivateIp() helper for reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:27:08 +00:00
DorianandClaude Opus 4.6 c21939b1f8 fix(app): block sensitive file reads in filesystem API
Add SENSITIVE_PATTERNS denylist to handleRead() in vite-fs.ts.
Blocks access to .env*, .git/, credentials, secrets, .pem, .key,
and SSH key files. Returns 403 for matched paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:26:01 +00:00
DorianandClaude Opus 4.6 4dc9588c8a fix(app): replace CORS Access-Control-Allow-Origin * with explicit localhost origin
Add setCorsHeaders() and handleCorsOptions() helpers in server/dev-auth.ts.
Replace wildcard CORS origin with http://localhost:5173 in all Vite plugins
and claude-proxy.ts. Include Authorization in allowed CORS headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:25:10 +00:00
DorianandClaude Opus 4.6 cc7d9fc19e fix(app): add dev server auth token to all API endpoints
Generate random VITE_DEV_API_TOKEN in dev.sh, validate Bearer token
in shared server/dev-auth.ts middleware. Applied to all Vite plugins
(fs, dev-chats, rss, web-search, tmdb, music-search) and claude-proxy.
Client-side uses apiFetch() wrapper to attach the token automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:23:17 +00:00
DorianandClaude Opus 4.6 b77c93607a feat(app): video player, guide page, free films, PWA cache fix
- Add VideoPlayerOverlay component for free film playback
- Add GuidePage with interactive node setup walkthrough
- Add freeFilms data catalog with public domain films
- Enhance PlayerBar with video support and queue management
- Add video player store for overlay state management
- Refactor music search plugin (Jamendo integration cleanup)
- Add PWA cache version purge mechanism in main.ts
- Add PWA icon cache fix skill for Brave/Chrome
- Improve content grids: loading states, image fallbacks
- Enhance useArchy composable with node context
- Update useNostr with relay pool management
- Expand chat store with guide conversation support
- Add test fixtures for guide and node demo prompts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:56:39 +00:00
DorianandClaude Opus 4.6 c84c0fb424 fix(app): passphrase dialog — remove CSP meta, check crypto.subtle availability
- Remove CSP meta tag from index.html (breaks Vite HMR, should be
  set via HTTP headers in production nginx instead)
- isCryptoEnabled() now checks crypto.subtle is available (undefined
  over HTTP on non-localhost origins)
- Add try/catch + error feedback to passphrase submit flow
- PassphraseDialog accepts error prop, focuses input on visible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 06:58:55 +00:00
DorianandClaude Opus 4.6 290aa047d7 perf(app): add file size guard and loading state to code file reader
Handles 413 status for files > 1MB with user-friendly error message.
Adds fileLoading and fileError state for loading indicator support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:51:10 +00:00
DorianandClaude Opus 4.6 04db18c35a perf(app): add 30s cache TTL to Bitcoin price fetcher
Skips redundant API calls when price was fetched within the last 30
seconds, reducing network requests while keeping data fresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:49:19 +00:00
DorianandClaude Opus 4.6 9032e8cd60 perf(app): lazy load PdfViewer and MapRenderer with loading skeletons
Uses defineAsyncComponent to lazy load heavy renderers, reducing
initial bundle size. Shows loading text while components load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:48:39 +00:00
DorianandClaude Opus 4.6 31241d8d12 fix(a11y): improve img alt text with artist/director/author context
Updates alt attributes to include contextual info: songs include artist,
films include year and director, books include author, TV series include
type label.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:47:35 +00:00
DorianandClaude Opus 4.6 91dbaee38c fix(a11y): color contrast audit — document ratios, fix critical text
Documents WCAG AA contrast ratios in main.css. Increases text-white/40
to /50 for settings labels, section headers, and loading states to
meet 4.5:1 minimum contrast ratio.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:46:27 +00:00
DorianandClaude Opus 4.6 3258c047d9 fix(a11y): add focus management and ARIA attributes to dialogs
Adds role="dialog", aria-modal, focus trap, auto-focus close button,
and Escape key handling to ZapDialog and SettingsModal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:44:11 +00:00
DorianandClaude Opus 4.6 3c709c143f fix(a11y): add aria-labels to all content grid card buttons
Adds descriptive aria-label to card buttons in SongGrid, FilmGrid,
TVSeriesGrid, PlaceGrid, BookGrid, PodcastGrid, NewsGrid, ImageGrid,
and AppsGrid for screen reader accessibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:41:43 +00:00
DorianandClaude Opus 4.6 2011fdcae0 fix(a11y): add aria-labels to chat message action buttons
Adds aria-label to all 6 icon-only buttons in ChatMessage.vue:
edit, regenerate, reply, branch, upvote, downvote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:39:00 +00:00
DorianandClaude Opus 4.6 b4b1f8faf5 docs(app): add Archy local search guide and HelpSection component
Documents how file types map to content surfaces, how ContextBroker
filtering works, and adds a reusable HelpSection UI component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:37:51 +00:00
DorianandClaude Opus 4.6 a60faedc48 feat(app): allow .claude folder in file browser tree
Updates vite-fs tree walker to include .claude directories so users
can browse CLAUDE.md, settings, hooks, and memory files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:36:14 +00:00
DorianandClaude Opus 4.6 e33cb359a0 feat(app): add file browser page with tree navigation and preview
Adds /browse route with project listing, recursive file tree with
expand/collapse, and file preview sidebar (desktop) / overlay (mobile).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:35:15 +00:00
DorianandClaude Opus 4.6 cfec1dcc13 fix(test): use type assertions for partial test data in useContentPanel tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:27:35 +00:00
DorianandClaude Opus 4.6 5d8ce56b1b test(app): add seed conversation regression tests
Validates extraction pipeline against all seed prompts. 15 tests
covering films, songs, books, TV, places, podcasts, images, code,
recipes, events. Verifies tag stripping completeness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:25:07 +00:00
DorianandClaude Opus 4.6 5c82a09e65 test(app): add content extraction edge case tests
Tests empty input, interleaved tags, malformed tags, unicode content,
duplicate deduplication, place/TV extraction, tag stripping, magazine
sections. 11 test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:23:49 +00:00
DorianandClaude Opus 4.6 c6e384a288 test(app): add unit tests for useVisualViewport composable
Tests keyboard detection, viewport height tracking, debounce behavior.
Includes withSetup test helper for composables with lifecycle hooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:22:02 +00:00
DorianandClaude Opus 4.6 1a9b769e45 test(app): add unit tests for useContentPanel composable
Tests tab switching, detail open/close, panel state management,
design system mode. 9 test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:21:12 +00:00
DorianandClaude Opus 4.6 7c8315f362 test(app): add unit tests for usePlayer composable
Tests queue management, state transitions, progress computation,
deduplication, and API shape. 9 test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:20:27 +00:00
DorianandClaude Opus 4.6 ece4540388 feat(app): add Content-Security-Policy meta tag
Restricts script, style, img, connect, media, and frame sources to
known-safe origins. Blocks object embeds and enforces base-uri self.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:18:58 +00:00
DorianandClaude Opus 4.6 e7496883c4 fix(app): replace innerHTML='' with textContent='' in usePlayer
Safer DOM clearing that avoids innerHTML for content sanitization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:18:29 +00:00
DorianandClaude Opus 4.6 2581f20ebf fix(app): add URL length limit (2048 chars) to extractUrlFromText
Prevents processing excessively long URLs in content extraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:18:00 +00:00
DorianandClaude Opus 4.6 2ced4830f6 fix(app): add postMessage origin validation to archyBridge
Configurable origin replaces wildcard '*' for both sending and receiving.
Origin check filters incoming messages when a specific origin is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:17:24 +00:00
DorianandClaude Opus 4.6 f84e68ad06 fix(app): add error handling to PdfViewer renderPage and VideoPlayer initHls
Prevents unhandled promise rejections from async watchers and lifecycle hooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:16:34 +00:00
DorianandClaude Opus 4.6 9b3fd5d973 fix(app): add catch block to SSE readSSE for stream read errors
Reports stream errors to onError callback instead of silently failing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:14:31 +00:00
DorianandClaude Opus 4.6 82f1e99324 fix(app): wrap unprotected JSON.parse calls in try/catch
SSE stream callbacks and AI response parsing now handle malformed data
gracefully instead of throwing unhandled exceptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:11:18 +00:00
DorianandClaude Opus 4.6 9ba4854024 fix(app): debounce viewport change handler, overflow hidden when keyboard open
50ms debounce prevents jittery resizing on mobile keyboard open/close.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:08:30 +00:00
DorianandClaude Opus 4.6 378b60820a fix(content): add .catch() to cover fetch promise chains in grids
Prevents unhandled promise rejections if fetch throws unexpectedly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:07:32 +00:00