feat(content): add places, code mode, mobile context tab, and detail views

- Add Places/Restaurants content type with PlaceCard, PlaceDetail, PlaceGrid
- Add WebsiteDetail and MagazineSectionDetail views for Context panel
- Enhance MagazineGrid hero with background image and 3x taller header
- Add mobile 3-tab layout (Chat, Content, Context) with detail navigation
- Add /code command system: useCodeContext composable, ProjectGrid, FileTreeNode,
  CodeDetail for IDE-style code viewing across all three panels
- Fix /code bubble and prompt index clicks to re-activate code mode
- Fix updatePanelFromText overwriting code tab by skipping command messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-03 09:31:18 +00:00
co-authored by Claude Opus 4.6
parent 48dd7a9c68
commit e8fc54cade
22 changed files with 1901 additions and 107 deletions
@@ -321,6 +321,22 @@ export async function fetchBookCover(
}
}
/** Place/restaurant fallback — map pin with cuisine hint */
export function generatePlaceFallback(name: string, cuisine?: string): string {
const hue = [...(name + (cuisine ?? ''))].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<rect width="200" height="200" fill="hsl(${hue}, 20%, 10%)"/>
<rect x="3" y="3" width="194" height="194" rx="12" fill="none" stroke="hsl(${hue}, 25%, 16%)" stroke-width="1"/>
<text x="100" y="72" text-anchor="middle" fill="hsl(${hue}, 20%, 22%)" font-family="system-ui,sans-serif" font-size="9" font-weight="500" letter-spacing="3">PLACE</text>
<g transform="translate(100 120) scale(1.8)" fill="none" stroke="hsl(${hue}, 35%, 40%)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M0-14C-7.7-14-14-7.7-14 0c0 10.5 14 22 14 22s14-11.5 14-22c0-7.7-6.3-14-14-14z"/>
<circle cx="0" cy="0" r="5"/>
</g>
${cuisine ? `<text x="100" y="170" text-anchor="middle" fill="hsl(${hue}, 25%, 40%)" font-family="system-ui,sans-serif" font-size="9" font-weight="400">${escapeXml(cuisine.length > 22 ? cuisine.slice(0, 20) + '…' : cuisine)}</text>` : ''}
</svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
export async function fetchMusicCover(
title: string,
artist: string,