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

11 KiB
Raw Permalink Blame History

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):

{
  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.

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)