docs(fold-in): spec for folding Timelapse + AI Usage into the Void
Cross-origin HTTPS iframe embeds as left-rail "Apps" items; standalone URLs stay chromeless. phuryn gets aiusage.hynesy.com behind CF Access; timelapse gets a Phase-1 palette/typography restyle. Targets alpha.27. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
# Design: Fold Timelapse + AI Usage into the Void
|
||||||
|
|
||||||
|
**Date:** 2026-06-08
|
||||||
|
**Status:** Approved (brainstorm), pending implementation plan
|
||||||
|
**Target version:** void-server `2.0.0-alpha.27`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Make the **Timelapse** app and the **AI Usage (phuryn/claude-usage)** dashboard
|
||||||
|
first-class, navigable items in the Void's left rail, embedded as **cross-origin
|
||||||
|
HTTPS iframes**. Each app remains reachable at its own `*.hynesy.com` URL, and
|
||||||
|
when accessed there it shows only the bare app — no Void chrome, so there is no
|
||||||
|
way to "backtrack" into the Void. The Timelapse app additionally gets a Phase‑1
|
||||||
|
restyle (palette + typography) to align with the Void aesthetic.
|
||||||
|
|
||||||
|
This is the **fold-in feature**. Three things are explicitly **out of scope**
|
||||||
|
here and tracked separately:
|
||||||
|
|
||||||
|
- The phuryn dashboard "not really working" **functional fix** (deferred by user).
|
||||||
|
- The Timelapse **Phase‑2 full visual match** (preview-first follow-up).
|
||||||
|
- **Void 1 / CT 301 teardown** (separate infra effort, sequenced after this).
|
||||||
|
- Moving the existing **Terminal** rail item into the new section.
|
||||||
|
|
||||||
|
## Background / current state
|
||||||
|
|
||||||
|
- **Void 2** — `/project/src/void-v2`, Express + hash-routed SPA, deployed to
|
||||||
|
CT 311 (`192.168.1.216`), served at `void.hynesy.com` behind CF Access.
|
||||||
|
- Sidebar (`public/components/sidebar.js`) has sections: Spaces, Agents, Navigate.
|
||||||
|
- **Embedding precedent:** the `Terminal` item is an `<iframe src="/terminal/">`
|
||||||
|
that `server.js` reverse-proxies (with WebSocket upgrade) to ttyd on CT 300.
|
||||||
|
- **Timelapse** — `/project/farm-timelapse`, FastAPI + HTMX (Python) on CT 108,
|
||||||
|
served at `timelapse.hynesy.com` (CF tunnel → Traefik on CT 100 → CT 108:8000,
|
||||||
|
behind CF Access / Google IdP).
|
||||||
|
- **AI Usage** — `phuryn/claude-usage`, a Python-stdlib web SPA on **CT 300**
|
||||||
|
(`192.168.1.212:8080`). Its data source is CT 300's local
|
||||||
|
`/root/.claude/projects/**/*.jsonl` (Claude Code transcripts). There is also a
|
||||||
|
Sacred Valley card `public/views/cards/ai_usage.js` whose `Full dashboard ↗`
|
||||||
|
link currently points at the raw `http://192.168.1.212:8080/`.
|
||||||
|
|
||||||
|
### Why phuryn stays on CT 300
|
||||||
|
|
||||||
|
Its entire data source is CT 300's local Claude Code transcripts. Relocating it
|
||||||
|
to the Void CT would require continuously syncing those JSONL files across hosts
|
||||||
|
for no benefit. Void already proxies to CT 300 for the Terminal, so CT 300 is an
|
||||||
|
already-trusted backend.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| Embed strategy | **A — cross-origin HTTPS iframes** | Both apps use root-relative paths; a same-origin prefix proxy would collide with Void's own routes (timelapse's hardcoded `/browse…`, phuryn's `fetch('/api/…')`). Cross-origin iframes to each app's own HTTPS origin sidestep all path rewriting. |
|
||||||
|
| phuryn hostname | **`aiusage.hynesy.com`** | New CF tunnel host → CT 300:8080 behind CF Access. Also *adds* auth to phuryn, which is currently unauthenticated on the LAN. |
|
||||||
|
| Timelapse standalone URL | `timelapse.hynesy.com` (unchanged) | Already HTTPS, no `X-Frame-Options`, directly frameable. |
|
||||||
|
| SV AI Usage card | **Keep as-is**, retarget its link only | Card stays; `Full dashboard ↗` retargets to in-Void `#/ai-usage`. |
|
||||||
|
| Rail placement | New **"Apps"** sidebar section | Groups embedded external apps; Terminal stays under Navigate (moving it is out of scope). |
|
||||||
|
| Timelapse restyle | **Phase 1 = palette + typography only** | Fast, low-risk, no markup restructure. Phase 2 deferred. |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Void renders, per app, a thin Void-styled header bar + a full-height `<iframe>`
|
||||||
|
pointing at the app's own HTTPS origin. No "back to Void" affordance is injected
|
||||||
|
into either app, so standalone visits stay chromeless.
|
||||||
|
|
||||||
|
| App | Standalone URL | Backend host | Iframe `src` |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Timelapse | `timelapse.hynesy.com` (exists) | CT 108 | `https://timelapse.hynesy.com` |
|
||||||
|
| AI Usage | `aiusage.hynesy.com` (new) | CT 300:8080 | `https://aiusage.hynesy.com` |
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Void (CT 311, `/project/src/void-v2`)
|
||||||
|
|
||||||
|
- `public/router.js` — add routes `timelapse` (`#/timelapse`) and `ai-usage`
|
||||||
|
(`#/ai-usage`).
|
||||||
|
- `public/components/sidebar.js` — add an **"Apps"** section with `Timelapse`
|
||||||
|
and `AI Usage` nav items (active-highlight synced like existing items).
|
||||||
|
- `public/views/timelapse.js` and `public/views/aiusage.js` — each mirrors
|
||||||
|
`public/views/terminal.js`:
|
||||||
|
- Void header bar: `◆ <Title>`, an `↗ Open` link to the standalone URL
|
||||||
|
(`target=_blank`), and a `⟳ Reload` button (`f.src = f.src`).
|
||||||
|
- Full-height `<iframe>` with correct `src`. Timelapse iframe gets
|
||||||
|
`allow="fullscreen"` for video playback.
|
||||||
|
- No back-to-Void affordance inside the embedded app.
|
||||||
|
- `public/app.js` — wire the two new view modules into the render switch
|
||||||
|
(matching how `terminal` is dispatched).
|
||||||
|
- `public/views/cards/ai_usage.js` (line ~33) — retarget `Full dashboard ↗`
|
||||||
|
from `http://192.168.1.212:8080/` to in-Void `#/ai-usage` (drop `target=_blank`
|
||||||
|
so it opens session-shared within the SPA). Card otherwise unchanged.
|
||||||
|
- `package.json` / version string in `server.js` — bump to `2.0.0-alpha.27`;
|
||||||
|
CHANGELOG entry.
|
||||||
|
|
||||||
|
### Cloudflare / infra (provisioned via CF API; creds in memory)
|
||||||
|
|
||||||
|
- **Traefik (CT 100) dynamic config:** router for `aiusage.hynesy.com` →
|
||||||
|
`http://192.168.1.212:8080`. Wildcard `*.hynesy.com` DNS already targets the
|
||||||
|
tunnel, so no new DNS record is required.
|
||||||
|
- **CF Access application** for `aiusage.hynesy.com` — Google IdP + email
|
||||||
|
allowlist, cloned from the existing `timelapse` Access app.
|
||||||
|
|
||||||
|
### Timelapse restyle — Phase 1 (CT 108, `/project/farm-timelapse`)
|
||||||
|
|
||||||
|
- Port Void's design tokens into `tlcapture/static/style.css`:
|
||||||
|
- Palette: `--bg #0a0a0e`, `--panel #14141c`, `--panel-2 #1c1c26`,
|
||||||
|
`--border #2a2a36`, `--text #e8e6ed`, `--muted #888094`,
|
||||||
|
`--accent #ff4f2e` (blackflame), `--ok/--warn/--bad` as in Void.
|
||||||
|
- Fonts: display `Cinzel`/`Cormorant Garamond` serif, body
|
||||||
|
`Cormorant Garamond`, UI `system-ui`, mono `JetBrains Mono`.
|
||||||
|
- Colors, typography, and surface backgrounds only. **No markup restructure**
|
||||||
|
(that is Phase 2). The app keeps its own internal navigation/controls.
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
No new data paths. phuryn keeps reading CT 300's local JSONL transcripts; only
|
||||||
|
its *exposure* changes (LAN `:8080` → additionally `aiusage.hynesy.com` via
|
||||||
|
Traefik/CF Access). Timelapse is functionally untouched; only its CSS changes.
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
Cross-origin iframes cannot report load status to the parent, so each Void view
|
||||||
|
always surfaces an `↗ Open in new tab` fallback in its header (usable even if the
|
||||||
|
frame is blank) plus a `⟳ Reload`. If CF Access ever shows a login wall inside
|
||||||
|
the frame, the fallback link routes the user there at top level. Validating that
|
||||||
|
CF Access SSO renders frame-inline (no login wall, given an existing session) is
|
||||||
|
the **first** implementation step.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- **vitest + jsdom:** router resolves `#/timelapse` and `#/ai-usage`; sidebar
|
||||||
|
renders the two Apps items; each view mounts an iframe with the expected `src`;
|
||||||
|
the SV card link points at `#/ai-usage`.
|
||||||
|
- **CF / infra:** `curl -I https://aiusage.hynesy.com` → CF Access challenge when
|
||||||
|
unauthenticated; `200` with a valid session/service token.
|
||||||
|
- **Playwright (webapp-testing):** click each rail item → embed loads; visit
|
||||||
|
`timelapse.hynesy.com` and `aiusage.hynesy.com` directly → no Void rail present.
|
||||||
|
|
||||||
|
## Sequencing & safety
|
||||||
|
|
||||||
|
1. Provision `aiusage.hynesy.com` (Traefik route + CF Access app); **validate
|
||||||
|
iframe SSO** renders without a login wall before touching Void.
|
||||||
|
2. Void changes: Apps section + routes + two views + SV-card retarget + version
|
||||||
|
bump → `alpha.27`. Deploy via existing `deploy/`.
|
||||||
|
3. Timelapse Phase‑1 restyle. Deploy via `scripts/install.sh`.
|
||||||
|
|
||||||
|
`pct snapshot` CT 311 before the Void deploy and CT 108 before the restyle
|
||||||
|
deploy (backup-before-changes rule).
|
||||||
|
|
||||||
|
## Out of scope (tracked elsewhere)
|
||||||
|
|
||||||
|
- phuryn functional fix ("not really working").
|
||||||
|
- Timelapse Phase‑2 full component restyle (preview first).
|
||||||
|
- Void 1 / CT 301 teardown (separate infra effort).
|
||||||
|
- Migrating the Terminal rail item into the Apps section.
|
||||||
Reference in New Issue
Block a user