Spec 004 — File browser

Status: Draft Related: PRD FR-19, bettergpt mockups

Goal

For any chat, show the chat's home directory as a tree. Tapping a file shows its contents (syntax-highlighted for code, rendered for markdown, image-preview for images). Inspiration: Cortex's mockups/bettergpt/index-split-view-filebrowser.html.

Routes

  • GET /api/chats/:chat_id/files/tree?path=<subpath> — JSON: [{name, path, type:"dir"|"file", size, mtime}]
  • GET /api/chats/:chat_id/files/content?path=<subpath> — raw bytes; client picks renderer from MIME/extension
  • POST /api/chats/:chat_id/files?path=<subpath> — write (later)
  • DELETE /api/chats/:chat_id/files?path=<subpath> — delete (later)

Backend runs each as sudo -u chat-<hex> → reads the chat user's home. Paths are always relative to home; .. is rejected via realpath-and-prefix check.

UI

Desktop / tablet

Three columns: [Messages | File tree | File viewer or Terminal]. Tree + viewer share ~50/50 of the right two-thirds; messages fixed 1/3.

Mobile

Swipe-tab layout. Tab 2 is the file browser; it splits vertically into tree (top 40%) + viewer (bottom 60%) once a file is selected. Back-gesture returns to messages.

Mockup references in docs/mockups/README.md.

Renderers

Type Renderer
Markdown react-markdown with remark-gfm
Code react-syntax-highlighter with Prism themes
JSON/YAML syntax-highlighted, collapsible tree
Image <img> with width 100%
PDF <iframe> or react-pdf (post-v1)
Binary "N bytes, not previewed" + download link

Hidden files

Show dotfiles by default (Jacob wants to see .picortex/prompts/discriminator.md). Exclude .git/objects/* (noise) behind a "show all" toggle.

Security

  • Path check: realpath(home + path) must start with realpath(home).
  • Enforce read-size cap (10 MB). Above that, return 413.
  • Content-type sniff server-side; never echo untrusted Content-Type headers from the filesystem.
  • HTML content is returned as text/plain unless explicitly requested as rendered markdown.

Caching

  • Tree responses cached with ETag (home-dir mtime + size-of-listing hash)
  • File content cached with ETag (file mtime + size)
  • Browser cache: Cache-Control: private, max-age=5

Testing

  • Unit: path-sanitizer rejects .., symlinks-out, absolute paths, null bytes.
  • Integration: real filesystem; navigate, assert sorted, assert rejected on another chat's home.
  • E2E: Playwright on mobile; tap through tree, render markdown.

Open questions

  • OQ1: Do we want write support in v1? (Yes, for editing .picortex/prompts/discriminator.md directly.) Post-S7.
  • OQ2: Search within a chat's files? (Defer to v0.2 — the terminal can rg.)
[[curator]]
I'm the Curator. I can help you navigate, organize, and curate this wiki. What would you like to do?