259 lines
11 KiB
Markdown
259 lines
11 KiB
Markdown
# AGENTS.md — Thornhill OS (v4)
|
||
|
||
Guidance for AI agents and contributors working in this repository.
|
||
|
||
## What this project is
|
||
|
||
**Thornhill OS** is a web-based recreation of *the Machine* UI from the TV series *Person of Interest*. Version 4 merges the Machine Point-of-View (MPOV) chrome from classic Thornhill OS with the keyboard-first feed mosaic and timeline from Thornhill TV into one Deno Desktop app.
|
||
|
||
- **Version:** 4.0.0
|
||
- **License:** CC BY-NC-SA 4.0
|
||
- **Stack:** Deno Desktop · Bun · Vue 3 (`<script setup>`) · Vite 8 · `destyle.css` · commander.js
|
||
- **Docs (canonical product reference):** sibling repo `thornhill-docs` — do not add a `docs/` tree here
|
||
|
||
Terminology (see thornhill-docs `Reference.md`):
|
||
|
||
| Term | Meaning |
|
||
|------|---------|
|
||
| **Asset** | People serving the Machine's purpose |
|
||
| **Open system** | Access to MPOV and communication from asset computers |
|
||
| **Admin** | The Machine's creator; controls the Machine via the terminal |
|
||
|
||
---
|
||
|
||
## High-level architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Deno Desktop │
|
||
│ • preload: src/preload.js (BrowserWindow + bindings) │
|
||
│ • HMR / packaging via `deno desktop` │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│ globalThis.bindings
|
||
┌──────────────────────────▼──────────────────────────────────┐
|
||
│ Vue 3 renderer (Vite) │
|
||
│ App.vue → Space + Timeline + Output + Terminal │
|
||
│ reactive store (src/store.js) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
There is **no** scriptable `lib/` API, **no** `default.js` auto-run, and **no** `@thornhill-corp/*` modules in v4. UI components render store state; windows/output start empty until something future populates them.
|
||
|
||
### Process / bootstrap
|
||
|
||
| Layer | Entry | Role |
|
||
|-------|-------|------|
|
||
| Desktop preload | `src/preload.js` | Create `Deno.BrowserWindow`, bind `shutdown` |
|
||
| HTML shell | `index.html` → `/src/main.js` | Mount Vue on `#app`, load `destyle.css` |
|
||
| Root UI | `src/App.vue` | Layout + global shortcuts |
|
||
| State | `src/store.js` | Shared reactive store |
|
||
| Commands | `src/helpers/commands.js` | commander.js terminal dispatch |
|
||
|
||
---
|
||
|
||
## Repository layout
|
||
|
||
```
|
||
thornhill-os-v4/
|
||
├── index.html # Shell: destyle, dark color-scheme, #app
|
||
├── package.json # Bun scripts + deps (commander, vue, destyle)
|
||
├── bun.lock
|
||
├── deno.json # Desktop app metadata + Vite tasks
|
||
├── vite.config.js # Vue plugin + node: shims for commander
|
||
├── scripts/build-desktop.sh # Multi-platform packaging → out/
|
||
├── .cursor/rules/
|
||
│ └── stack-and-tooling.mdc # Stack / Bun / run / build rules (always apply)
|
||
└── src/
|
||
├── main.js
|
||
├── App.vue # Grid layout + Space/Esc/F5/Tab
|
||
├── store.js
|
||
├── preload.js # Deno Desktop window + bindings.shutdown
|
||
├── bindings.d.ts # Types for globalThis.bindings
|
||
├── helpers/
|
||
│ ├── colors.js # Named palette + prop validator
|
||
│ └── commands.js # Terminal → commander (about/help/shutdown)
|
||
├── shims/ # Browser shims for commander’s node: imports
|
||
├── components/
|
||
│ ├── Space/ # Feed mosaic ↔ windows view
|
||
│ ├── Window/ # Machine window chrome + body widgets
|
||
│ ├── Terminal/ # Admin terminal overlay
|
||
│ ├── Timeline.vue
|
||
│ ├── Output.vue
|
||
│ └── LoadingTextDots.vue
|
||
└── static/ # Futura, Monaco, Apple fonts + images
|
||
```
|
||
|
||
---
|
||
|
||
## UI model
|
||
|
||
### Composition (`App.vue`)
|
||
|
||
Full-viewport CSS grid: `1fr` main + `125px` Timeline strip.
|
||
|
||
| Child | Role |
|
||
|-------|------|
|
||
| **Space** | Feed mosaic (default) or Machine windows (after feed select) |
|
||
| **Timeline** | Year/month navigator (Tab toggle) |
|
||
| **Output** | Full-screen / overlay text with character reveal |
|
||
| **Terminal** | Admin overlay (`v-if` when open) |
|
||
|
||
Fonts: **Futura** (UI), **Monaco** (terminal), **Apple** (timeline). Design tokens as CSS variables on `:root` in `App.vue` (`--color-*`, `--font-*`).
|
||
|
||
### Space: feed view ↔ windows view
|
||
|
||
| Mode | Visible | Background |
|
||
|------|---------|------------|
|
||
| **feeds** (default) | Feed mosaic only | Black |
|
||
| **windows** | `SpaceWindows` only | Selected feed tile color |
|
||
|
||
**Selection (feed view)**
|
||
|
||
- Click tile → enter windows view
|
||
- **ZQSD** (AZERTY WASD): move selection cursor; highlight via `transform: scale` (layout unchanged)
|
||
- **Enter** → zoom into selected tile, then windows view
|
||
- After pan/`+/-` zoom, if the selected tile leaves the viewport, selection moves to the **nearest still-visible** tile
|
||
|
||
**Zoom**
|
||
|
||
- Shared transform on the feeds layer (`translate` + `scale`)
|
||
- `+` / `-` manual zoom in feed view
|
||
- Enter/exit windows view reuses the same transform for zoom-in / zoom-out animation (~350ms)
|
||
|
||
Feeds are placeholders (random hex colors). Timeline does not filter feeds yet.
|
||
|
||
### Escape / Space priority
|
||
|
||
1. Terminal open → Esc closes terminal
|
||
2. Else windows view → Esc exits to feeds (zoom out)
|
||
3. Space key opens terminal from either view when closed
|
||
|
||
---
|
||
|
||
## Keyboard map
|
||
|
||
| Context | Keys | Behavior |
|
||
|---------|------|----------|
|
||
| Global | Space | Open terminal (when closed) |
|
||
| Global | Escape | Close terminal, else exit windows → feeds |
|
||
| Global | F5 | Reload |
|
||
| Global | Tab | Toggle Timeline active + focus Space ↔ Timeline |
|
||
| Feed view (Space focused) | Arrows | Continuous pan |
|
||
| Feed view | Z / Q / S / D | Move feed selection |
|
||
| Feed view | Enter | Enter windows for selection |
|
||
| Feed view | Click tile | Select + enter windows |
|
||
| Feed view | `+` / `-` | Zoom in / out |
|
||
| Timeline focused | Arrows | Year / month navigation |
|
||
| Terminal entry | Enter, ↑/↓, Ctrl+L, Ctrl+C | Submit, history, clear, cancel |
|
||
|
||
Ignore feed navigation / zoom while the terminal is open or Timeline is focused (except Esc / Space / F5 / Tab as above). Prefer DOM listeners on `window` / component roots (not template `@keydown` for focus-scoped controls). Clean up in `onBeforeUnmount`.
|
||
|
||
---
|
||
|
||
## Terminal (commander.js)
|
||
|
||
UI: `Terminal.vue` + `TerminalMainEntry.vue` — overlay chrome, `#` comments, trailing ` &`, history in `store.history`.
|
||
|
||
Dispatch: `src/helpers/commands.js` builds a commander `Command` per line (`exitOverride`, capture writeOut/writeErr). Native commands only:
|
||
|
||
| Command | Behavior |
|
||
|---------|----------|
|
||
| `about` | `Thornhill O.S. v${version}` from `package.json` |
|
||
| `help` | Lists `about`, `help`, `shutdown` |
|
||
| `shutdown` | `await globalThis.bindings.shutdown()` (Deno Desktop quit via preload) |
|
||
|
||
Unknown command → `Command not found`. No `run`, no module loading.
|
||
|
||
**Commander in the browser:** Vite aliases `node:*` imports to `src/shims/*` so commander bundles for the webview. Keep those shims if commander stays a dependency.
|
||
|
||
---
|
||
|
||
## Store (`src/store.js`)
|
||
|
||
Reactive module (no Vuex / Pinia):
|
||
|
||
```js
|
||
{
|
||
spaceSize: { width, height },
|
||
output: string[],
|
||
isOutputOverlay: boolean,
|
||
windows: [], // plain objects bound into Window.vue
|
||
history: [], // terminal history
|
||
viewMode: 'feeds' | 'windows',
|
||
selectedFeedIndex: number | null,
|
||
activeFeedColor: string | null,
|
||
isTerminalOpen: boolean,
|
||
isTimelineActive: boolean,
|
||
}
|
||
```
|
||
|
||
Helpers: `setSpaceSize`, `clearOutput`, `replaceOutput`, `appendOutput`, `addWindow`, `removeWindow`, `clearWindows`.
|
||
|
||
Zoom / pan offsets live in `Space.vue` (local), not the store.
|
||
|
||
---
|
||
|
||
## Window components
|
||
|
||
Machine windows under `src/components/Window/`. Body widgets are resolved via a **component map** + `<component :is>` in `Window.vue` (not string-global registration).
|
||
|
||
Body types: Separator, Subtitle, Text, Picture, Meter, Table, Countdown, ScrollingData.
|
||
|
||
Colors must use the named palette in `helpers/colors.js` (`transparent`, `black`, `white`, `gray`, `darkgray`, `darkergray`, `red`, `yellow`, `green`, `blue`). Header progress and subtitle remain mutually exclusive.
|
||
|
||
---
|
||
|
||
## Vue / code conventions
|
||
|
||
- Vue 3 **`<script setup>`** Composition API only (match the stack rule).
|
||
- No Electron, React, or a second bundler unless explicitly requested.
|
||
- Prefer Bun for dependency changes (`bun add` / `bun remove`); keep `bun.lock`; do not add npm/yarn lockfiles.
|
||
- Product look-and-feel and lore live in **thornhill-docs**; keep this repo’s UI show-accurate.
|
||
- Do not reintroduce `lib/`, `default.js`, or `@thornhill-corp/*` module loading unless the product direction changes explicitly.
|
||
- License is **non-commercial** (CC BY-NC-SA 4.0).
|
||
|
||
---
|
||
|
||
## Development workflow
|
||
|
||
Stack details: `.cursor/rules/stack-and-tooling.mdc`.
|
||
|
||
```bash
|
||
bun install # first time / after lockfile changes
|
||
bun run start # deno desktop --hmr --preload ./src/preload.js .
|
||
bun run build # all platforms → out/
|
||
bun run build-linux # (also build-macos / build-windows)
|
||
```
|
||
|
||
`deno task dev` / `deno task build` are Vite-only; prefer `bun run start` for desktop HMR.
|
||
|
||
Output basenames under `out/` must not contain dots (Deno webview launcher strips after the last `.`).
|
||
|
||
### Verification (no automated suite)
|
||
|
||
After UI or keyboard changes, manually check:
|
||
|
||
1. Feed pan, `+/-` zoom, ZQSD selection scale, viewport re-home
|
||
2. Enter/click → zoom into windows (bg = tile color); Esc → zoom out
|
||
3. Tab timeline; Space/Esc terminal; `about` / `help` / `shutdown`; F5
|
||
|
||
### Do not commit
|
||
|
||
`node_modules/`, `dist/`, `out/`, `.deno*`.
|
||
|
||
---
|
||
|
||
## Quick orientation map
|
||
|
||
| Task | Start here |
|
||
|------|------------|
|
||
| Feed mosaic / zoom / selection | `src/components/Space/Space.vue`, `Feed.vue` |
|
||
| Machine window chrome / body widgets | `src/components/Window/**` |
|
||
| Output animation | `src/components/Output.vue` |
|
||
| Terminal UX / commands | `src/components/Terminal/**`, `src/helpers/commands.js` |
|
||
| Timeline | `src/components/Timeline.vue` |
|
||
| Global shortcuts / layout | `src/App.vue` |
|
||
| Shared state | `src/store.js` |
|
||
| Desktop quit / window | `src/preload.js`, `src/bindings.d.ts` |
|
||
| Stack / deps / packaging | `.cursor/rules/stack-and-tooling.mdc`, `package.json`, `deno.json` |
|
||
| Product docs / screenshots | `thornhill-docs` (sibling repo) |
|