thornhill-os-v4/AGENTS.md
KaKi87 f7e65ed22e 📝 Add AGENTS.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 00:05:53 +02:00

259 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 commanders 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 repos 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) |