FRAMESHIFT
A local-first flight companion for Elite Dangerous: journal telemetry, a self-hosted trading engine, and a touch-ready cockpit panel — one Python process, no accounts, no cloud.
Overview
Frameshift (formerly Elite Trader) is a companion application for the space simulator Elite Dangerous. It reads the game's on-disk journal files in real time, maintains a local market database seeded from a community galaxy dump and kept fresh by a live data firehose, and serves a web cockpit to any browser on the LAN — most importantly a tablet mounted next to the HOTAS, running the touch-first flight panel mode.
Design philosophy
- Local-first. No account, no API key, no cloud backend. Every feature works from data on the player's own disk. Trade routes are computed locally against a 1.5 GB SQLite database rather than by calling a third-party website.
- Panel-first. The tablet cockpit is the primary interface; the desktop layout is the secondary view. UI decisions are made for touch at arm's length.
- Honest data. Prices carry confidence chips and conservative ranges built from age and order-book depth. The app labels what it estimates rather than presenting guesses as facts.
- Anti-bloat. A tab must justify itself with a self-contained multi-card workflow; features that duplicate mature external tools are declined. The rail is capped at ~12 icons.
- Teach the game. UI copy explains mechanics in plain terms (engineering rolls, exobiology bonuses, neutron plotting) — the target user includes brand-new commanders.
Technology stack
- Language
- Python 3.12 (backend) · native ES2022 modules checked in strict TypeScript mode, no framework (frontend)
- Server
- Flask 3 behind Werkzeug's threaded WSGI server, LAN-bound
- Storage
- SQLite (WAL mode) — two databases plus JSON stores; no external services
- Desktop shell
- pywebview window hosting the same web UI; also runs fully headless
- Realtime feed
- ZeroMQ subscriber on the EDDN community relay
- Packaging
- PyInstaller one-file Windows executable, built and checksummed by GitHub Actions
- Dependencies
flask,pywebview,requests,pyzmq,pydirectinput(Windows-only) — all version-pinned
Sections 01–03 explain how data moves. Sections 04–06 cover storage and the trust model. Sections 07–11 cover the UI, integrations and the delivery pipeline. Sections 12–13 provide measured source maps and links to the living contribution contract. Figures are numbered and referenced from the text.
System context
One Python process sits between three worlds: the game's files and
input queue on the same machine, the community network
(EDDN, Spansh, GitHub), and the player's devices on the LAN.
Everything the process learns converges on one thread-safe snapshot (AppState) and two
SQLite databases; everything the player sees is a view over those.
Three properties fall out of this shape and are enforced throughout the codebase:
- The game is read-only, except autoplot. Frameshift never writes to game files. The single write path into the game is the autoplot keystroke sequence (§09), which is explicit, user-triggered and abortable.
- The internet is optional. With networking removed, journal telemetry, the local trading engine, ops boards and analytics keep working. Network-backed features (Spansh planners, EDDN freshness, updates) degrade with explicit UI messaging.
- Devices are untrusted by default. Every request passes the security ladder in §06 — including requests from the desktop's own window, which are simply granted the loopback rung.
Runtime model
app.py is the composition root. It parses flags (--headless, --port),
refuses to double-start (a live /api/state probe on the target port opens a window to the existing
instance instead), then wires the runtime in a fixed order so that every consumer finds its producer already alive:
Thread inventory
| Thread | Owner | Cadence | Responsibility |
|---|---|---|---|
| journal-watcher | journal.py | 1 s poll | Bootstrap replay, live journal tail, Status/Cargo/Market/NavRoute/ShipLocker polling, game-process probe (15 s) |
| bio-community | journal.py | per arrival | Background Spansh fetch of community exobiology signals for the current system |
| eddn-listener | eddn.py | continuous | ZeroMQ subscribe to the EDDN relay; applies commodity/3 messages to market.db (60 s recv timeout, 10 s reconnect) |
| eddn-upload ×n | eddn_upload.py | per event | Fire-and-forget senders publishing privacy-scrubbed schema messages |
| http-server | server.py | per request | Werkzeug threaded WSGI serving UI + API |
| db-seed | seed.py | on demand | Downloads the ~4 GB Spansh galaxy dump and imports it into a sidecar DB, then swaps it in under a lock |
| updater | updater.py | hourly check | GitHub release polling, download + SHA-256/size/MZ verification, swap-script handoff |
| tts-download | tts.py | on demand | Fetches the pinned Piper binary + voice model; a persistent Piper child process synthesizes speech |
| extension pool | extensions.py | per event | ThreadPoolExecutor evaluating declarative extension rules against journal events |
Cross-thread discipline is simple and consistent: every shared structure is owned by exactly one
module. AppState guards all mutation behind one lock and hands out deep-copied snapshots;
SQLite connections are opened per-operation (or thread-locally shared inside a reconstruction session — §04);
the seed importer and the EDDN listener coordinate a database swap with an explicit pause/resume handshake so
a mid-import commodity message can never write into a half-built file.
Journal pipeline
Elite Dangerous appends one JSON event per line to a session journal and rewrites small status files in place. Frameshift's entire knowledge of the player comes from replaying and tailing these files — there is no game API. The pipeline has two phases with one invariant: ingestion is idempotent, so bootstrap and live tailing may overlap, crash and re-run without double counting.
Bootstrap file selection
The walk-back starts at the newest journal and keeps adding older files until it has at least
BOOTSTRAP_MIN_FILES (12) sessions and every context boundary is satisfied — the most recent
LoadGame, unsold exobiology samples (a SellOrganicData/Died boundary), and
colonisation-depot context that spans sessions — capped at BOOTSTRAP_MAX_FILES (25). Replaying ~12
files every start is therefore by design: it is how session-spanning state (massacre stacks, unsold
samples, carrier context) survives a restart without a hidden cache that could drift from the truth.
Commander attribution
Every event is attributed to a stable local profile key derived from the journal's commander identity
(commander_id — never a network identity). Stub sessions that never reach Commander/LoadGame
(menu-only launches) are marked processed but not ingested, and per-file bookkeeping tables never migrate during
profile repair — the two rules that keep the "unassigned history" counter honest.
Cold bootstrap on a mechanical disk was profiled end-to-end in v2.1.3: the dominant costs were per-connection
fsync and a schema-upgrade write transaction taken on every connect. The fixes — WAL +
synchronous=NORMAL, a write-free fast path when the stored schema version matches, and a thread-local
shared connection for the reconstruction session — took a 12-journal replay from ~6 minutes to ~39 seconds on
the same hardware.
Data layer
Two SQLite databases with sharply different lifecycles. market.db is rebuildable —
a cache of the galaxy that can be re-seeded from scratch at any time. commander.db is
durable — the player's own history, which nothing can regenerate. The code treats them with matching
levels of care: the market DB is swapped wholesale under a lock; the commander DB is migrated in place with
transactional, key-checked table rebuilds.
The event ledger
eventledger.py is the durable counterpart to the ephemeral AppState: every useful
journal event is stored once as canonical JSON compressed with zlib, keyed by a content digest. This is what makes
features retroactive — when a new analytics view or workflow ships, a replay over the ledger rebuilds its
derived tables from the player's full history instead of starting empty. The compressed payloads also make the
bootstrap/live overlap idempotent by construction.
Other stores
- security.json
- Paired-device registry — token digests only, atomic writes (§06)
- settings store
- User settings with validated keys (
settings.py) - data/tts/
- Piper binary + voice models, SHA-256 pinned downloads
- data/extensions/
- Declarative extension packs, fingerprint-approved (§08)
- data/webview/
- Desktop window profile — pywebview runs with
private_mode=Falseso localStorage survives - data/backups/
- Automatic pre-migration copies of commander.db
HTTP API
The Flask server exposes 89 routes under /api/, plus the static UI.
Authorization is default-deny: a route added tomorrow gets a scope requirement automatically —
unlisted GETs require READ, unlisted mutations require
CONTROL, and only two endpoints (pairing itself) are public.
Long-running search POSTs that mutate nothing are explicitly allow-listed back down to read.
Commander ownership is route metadata, not an informal client convention. 46 routes always require
an X-Frameshift-Commander identity and one extension-test route requires it conditionally when the
request reads journal history. The server captures one state snapshot when the request begins, rejects a missing or
stale identity, and never falls back to a mutable active profile during a journal handoff. A policy audit enumerates
every route and fails when ownership metadata is missing.
| Endpoint | Method | Scope | Purpose |
|---|---|---|---|
| Live state & suggestions | |||
| /api/state | GET | read | The full cockpit snapshot; polled every 1.5 s by every device |
| /api/suggest | GET | read | System/station name autocomplete (indexed prefix search) |
| /api/alerts · /alerts/clear | GET/POST | read/ctl | Watch-triggered price alerts |
| Trading engine (local database) | |||
| /api/trade-route | POST | read | Profit/hour loop & multi-hop chain planner with confidence ranges |
| /api/commodity-search | GET | read | Buy/sell search near any system |
| /api/mining · /mining/hotspots | GET | read | Mining advisor + nearest ring hotspots |
| /api/commodities | GET | read | Commodity catalog for autocomplete |
| /api/cargo-sell · /cargo-recovery | GET/POST | read | Best buyers for the current hold; stranded-cargo rescue |
| /api/station-market · /price-history | GET | read | Any station's full market; sparkline history |
| /api/system-stations | GET | read | Station facts for a system (pads, services, economy) |
| /api/colonisation-sources | GET | read | Parallel source search for construction-depot needs |
| /api/watch · /watch/remove | POST | ctl | Live EDDN route watches |
| Route planners (Spansh-backed) | |||
| /api/neutron · /riches · /exobio-route | POST/GET | read | Neutron highway, Road-to-Riches, exobiology route |
| /api/station-search | GET | read | Nearest outfitting/shipyard stock |
| /api/sell-data · /interstellar-factors · /material-traders | GET | read | Nearest service stations by kind |
| /api/exobio-genera | GET | read | Genus catalog for route filters |
| Commander workspaces | |||
| /api/engineering (+ /pin) | GET/POST | read/ctl | Blueprint planner, wishlist, material coverage |
| /api/objectives (+ /plan, delete) | all | ctl | Objectives CRUD + time-budgeted session planner |
| /api/operations (+ export/import, delete) | all | ctl | Shared ops boards with deterministic JSON merge |
| /api/specialists/* (9 routes) | GET/POST | ctl | Mining/combat/carrier/exobiology role workspaces |
| /api/analytics · /history/* · /timings | GET | read | Earnings analytics, ledger queries, learned activity timings |
| /api/loadout-export | GET | read | EDSY/Coriolis SLEF export of the current ship |
| Game control | |||
| /api/plot · /plot/cancel | POST | ctl | Autoplot a route in the game (§09); abortable |
| /api/launch-game | POST | ctl | Start Elite via Steam / Frontier launcher — fixed command, never user input |
| /api/speak · /tts/status | POST/GET | ctl/read | Neural voice synthesis (Piper) |
| Security & profiles | |||
| /api/security/status · /pair | GET/POST | public | Pairing gate — the only unauthenticated routes |
| /api/security/pairing-code · /devices* | all | admin | Issue/rotate pairing, list/rename/revoke devices |
| /api/security/session | DEL | read | Any device may revoke its own credential |
| /api/profiles (+ assign/activate/delete) | all | admin | Commander profile repair & management |
| Platform administration | |||
| /api/marketdb/status · /seed | GET/POST | read/adm | Database health; trigger the 4 GB seed import |
| /api/update/check · /apply · /status | mixed | read/adm | Self-update lifecycle |
| /api/settings | GET/POST | admin | Validated settings store |
| /api/extensions (+ reload/approve/revoke) | all | admin | Extension pack management |
| /api/extensions/save · /test · /<id>/manifest · delete | all | admin | In-app extension builder: save/edit/remove declarative packs; dry-run rules over recent ledger history |
| /api/diagnostics/health · /bundle | mixed | admin | Health probe; redacted support bundle |
| /api/journal-dir/validate | GET | admin | Probe a journal directory before adopting it |
| /api/tts/download · /voice | POST | admin | Voice model install / switch |
Error handling is uniform: routes return error_response() bodies built from
UserFacingError subclasses (ValidationError, NotFoundError). Exception text
is only echoed to a client when a developer explicitly marked the message as written for the player — an
unexpected exception can never leak internals into a response (CWE-209 by construction).
Security model
Frameshift is its own edge server on a home LAN, reachable by anything on the network and — via the browser — by any website a LAN user visits. The trust model is deliberately account-free: the desktop is trusted because it is local; everything else earns a capability. Every request climbs the same ladder:
Hardening conventions
- Path confinement. Every filesystem sink that touches a user-influenced path normalizes and
then executes inside a positive
startswith(root)branch, keeping the trust boundary explicit for review, static analysis and regression tests. - No command injection surface. The game launcher runs a fixed Steam/Frontier command; user input never reaches a shell.
- Supply-chain pinning. Runtime dependencies are version-pinned; GitHub Actions are pinned to commit SHAs; TTS binaries and voice models are verified against hard-coded SHA-256 digests before use.
- Update integrity. A release download must match the published size, carry an MZ header and match its published SHA-256 before the swap script is ever written (§10).
- Privacy-safe telemetry out. EDDN uploads are built per-schema from explicit field lists — commander identity never leaves the machine (§08).
Frontend
The browser application is 134 native ES modules (20,895 physical lines) behind a 21-line HTML shell. It remains framework-free and zero-build: Flask serves the source graph directly and PyInstaller packages it recursively. There are no runtime packages, generated bundles, CDN assets or transpilation step; npm is used only for pinned development gates. The same application state renders two layouts:
Flight panel mode — default
A touch cockpit for a tablet: a 100 px icon rail navigates 12 pages (STATUS, TRADE, MARKET, EXPLORE, GUIDES, STATS, ENG, GALAXY, OPS, ROLES, LOCAL, SETTINGS), a sticky status strip shows system/station/destination with fuel and cargo minibars on every page, and swipe gestures page the rail. Cards get corner brackets, a boot splash, and optional CRT ambience (off by default — ambient motion is opt-in). Touch targets are ≥46 px; number inputs render in the mono face.
Desktop mode
The same cards in an 11-tab layout for keyboard-and-mouse use, hosted either in any browser or in the pywebview desktop window.
Module and state architecture
- Composition
src/main.jsis a bootstrap-only entrypoint. Named modules underbootstrap/mount views, wire controls, coordinate state and start polling; they do not own feature DOM selectors or listeners.- Application state
core/store.jsowns one typedApplicationStatesnapshot. Features subscribe to complete snapshots and keep mutable workspace state in narrow feature-owned modules rather than ambient globals.- Transport
- Twelve named domain clients import explicit request/response contracts. Only
core/http.jsmay callfetch(); presentation code cannot embed API paths or import the transport directly. - Commander handoff
- Scoped requests capture the stable commander ID plus store generation, send
X-Frameshift-Commander, abort on profile change, and reject stale responses before feature state can be committed. - Safe rendering
- Escaping
html`…`templates and one reviewedrender()boundary own dynamic markup. Direct HTML sinks are prohibited; runtime values use escaped templates, text nodes or typed DOM helpers. - Feature ownership
features/owns domain behavior,features/views/owns static panes,features/controls/owns idempotent listener wiring, andshell/owns the desktop/Panel presentations.- CSS graph
- Twenty-six split sheets plus the reusable Holo Button component load through one entrypoint with declared layers: reset → tokens → base → components → features → panel → themes → motion. Every split sheet stays under 400 lines.
- Performance
- The shipped graph is 134 modules and 462 relative import edges with zero cycles. CI budgets module count, total JS/CSS/HTML bytes and the largest individual module for tablet startup.
core/data ← api ← features/shell ← bootstrap ← main. Features and shell share one
presentation rank; every relative import is resolved and classified, and the 462-edge graph must remain
acyclic. Strict TypeScript checkJs, explicit API contracts, safe rendering and browser-level
behavior tests provide the discipline without adding a framework or production build pipeline. The
living UI architecture
contract records the exact contribution rules.
External integrations
EDDN — inbound eddn.py
A ZeroMQ subscriber on the community relay (tcp://eddn.edcd.io:9500) applies
commodity/3 messages to market.db for any station whose system the seed knows —
zlib-inflated, schema-checked, fleet carriers and legacy-galaxy clients filtered. Net effect: local prices are
minutes old, not days, without Frameshift ever asking a website for a price.
EDDN — outbound eddn_upload.py
Frameshift also gives back. Docked market snapshots are contributed by default; broader journal/snapshot
schemas are an explicit opt-in. Every message is built for a concrete schema from allow-listed fields
rather than forwarding raw game JSON — commander-specific fields cannot leak, and location augmentation fails
closed. Uploads are stamped with the real gameversion/gamebuild captured from the journal header.
Spansh spansh.py
Two very different uses: the one-time galaxy dump seed (~4 GB download, ~15 min import — §04), and per-request planner calls (neutron route, Road-to-Riches, exobiology bodies, station/service search, per-system dumps for community bio signals). Named reference systems are primary; known coordinates are retried when Spansh doesn't index a fresh discovery. All calls carry timeouts and surface as user-facing errors, never tracebacks.
GitHub Releases updater.py
Hourly update checks against the Releases API; the download/verify/swap dance is described in §10. The legacy repository name 301-redirects, and every release publishes assets under both the old and new executable names — an install from any historical version can still update forward.
Piper TTS tts.py
Neural voice callouts run entirely locally: a pinned Piper build and voice model (6-voice catalog, SHA-256 verified) are downloaded on demand; one persistent child process synthesizes phrases at ~0.3× real-time with a WAV cache, so every paired device hears the same voice with no cloud round-trip.
Extension host extensions.py
Extension packs are declarative by default: a manifest that can inspect journal events and emit alerts or objective suggestions, but cannot execute code — safe to install by copying a directory. An optional process adapter exists for advanced integrations and stays disabled until the user approves the pack's exact content fingerprint; the child receives a minimal JSON document on stdin and has no API access. A capability boundary, not an OS sandbox — and documented as exactly that.
Autoplot
The feature that makes every system name in the UI a button: ◎ PLOT types a
route into the game itself. Elite Dangerous has no API for this, so autoplot.py drives the galaxy
map with synthetic keystrokes (pydirectinput), reading the player's own keybindings
(bindings.py) rather than assuming defaults.
The sequence — open map, focus search, type the name, commit, zoom, plot — takes several seconds, so it is
engineered to be safe to trigger from a tablet and safe to abort: the requesting control turns
into a CANCEL button, /api/plot returns 409 when the game isn't running, and success is
verified rather than assumed by watching NavRoute.json for the expected destination.
A voice callout confirms the plotted route.
Build & release
Releases are cut by pushing a v* tag. Nothing is built by hand, and the pipeline will not
produce an executable from a failing gate. The tag build repeats strict frontend checks, 113 Vitest cases,
16 Chromium/WebKit browser executions and all 55 isolated Python test scripts before it stamps the version.
After packaging, the executable must serve a coherent recursive ESM graph with the expected cache and version
headers before GitHub receives an asset:
test.yml) on both Windows and Ubuntu.Quality engineering
Layered test suite 87 files · 3 runtimes
Each test layer owns the behavior it can observe best. Python exercises backend state and persistence, Vitest imports the real ESM modules against a lightweight DOM, and Playwright verifies complete desktop and tablet journeys in actual browser engines.
| Layer | Files | Executions | Contract |
|---|---|---|---|
| Python | 55 | isolated | Journal, database, API, security, migration and process lifecycle. Every script runs in a fresh process. |
| Vitest | 31 | 113 | Store, API clients, safe rendering, feature ownership, profile handoffs, accessibility and UI state. |
| Playwright | 1 | 16 | Full Chromium journeys plus tagged WebKit tablet coverage with an iPad Pro landscape profile. |
The Python runner parses each script, records every local test_* definition and fails if the
entrypoint did not actually call one. This preserves the application's required process isolation without
permitting silent test definitions.
Risk-shaped coverage
- Journal interpretation — event handlers, bootstrap selection, recovery, rebuild progress, commander scoping and profile-repair regressions (the "48 unassigned rows" class of bug gets a permanent test).
- Data integrity — ledger idempotency, migration rebuilds, EDDN schema validation against the official schemas, reseed swap safety.
- Security and identity — pairing, scopes, host/origin checks, path confinement, updater verification, profile activation coherence and the 46-required/one-conditional commander-route policy.
- UI behavior — source modules are imported directly. Store generation, scoped requests, feature handoffs, escaping, focus behavior, reduced motion and the specialist/tablet paths are asserted as behavior rather than source-text tokens.
Static and release gates
- Architecture — strict TypeScript
checkJs, explicit API contracts, module-size limits, no legacy bridges or type exemptions, and no presentation-layer transport or unsafe DOM seam bypasses. - Graph integrity — all 462 relative imports resolve, follow the permitted layer direction and form zero cycles; bare runtime package imports are rejected.
- UI budgets — DOM IDs, CSS layers/import order, module count, JS/CSS/HTML weight, formatting and lint rules are deterministic CI failures rather than review suggestions.
- Browser matrix — Chromium runs the complete journey suite; WebKit runs the tagged tablet path. Fixed locale, time zone, reduced motion and retained traces keep failures reproducible.
- Packaged smoke — the frozen executable must recursively serve its same-origin ESM graph with one stamped version,
no-cacheUI assets andno-storeAPI state. - CI matrix — every push/PR compiles Python and runs frontend, browser and isolated-Python gates on Windows and Ubuntu; the release job repeats them before building.
- Profiling culture — performance work starts with py-spy on the live process (it attaches to the frozen exe), not with guesses; the two big wins (idle CPU ~51%→0%, cold start 6 min→39 s) both came from profiler evidence overturning the obvious suspects.
Module reference
The backend's 44 modules contain 20,240 physical lines concentrated around journal interpretation, HTTP policy and local data. The frontend is intentionally distributed: 20,895 lines across 134 bounded modules rather than one application file.
| Module | LOC | Responsibility |
|---|---|---|
| Game interface | ||
| journal.py | 3,002 | Journal watcher: bootstrap replay, live tail, ~90 event handlers, status-file polling, game-process probe |
| autoplot.py | 377 | Galaxy-map keystroke driver with NavRoute verification |
| bindings.py | 167 | Reads the player's own keybinding files for autoplot |
| launcher.py | 157 | Game-running probe; Steam/Frontier launch (fixed command) |
| flight.py | 218 | Fuel/scoop planning, surface-distance math, jumponium recipes |
| Server & security | ||
| server.py | 2,037 | Flask app: 89 API routes, commander policy, scope/host/origin checks, static UI |
| security.py | 325 | Pairing capabilities, device registry, scopes, rate limiter |
| network.py | 313 | LAN pairing URLs and QR provisioning |
| errors.py | — | User-facing error taxonomy (leak-proof responses) |
| diagnostics.py | 218 | Health probe; redacted support bundle |
| Data & market engine | ||
| marketdb.py | 1,272 | market.db schema/queries, seed swap, profile administration |
| commanderdb.py | 728 | commander.db schema, transactional migrations, profile repair |
| eventledger.py | 620 | Compressed journal history; replay primitives |
| routes.py | 888 | Local trade-loop/chain planner, commodity search, cargo recovery |
| mining.py | 400 | Mining advisor (method tagging, buyer ranking) |
| seed.py | 229 | Galaxy-dump download/import thread with progress |
| alerts.py | 248 | EDDN-driven route watches and alert baselines |
| Community network | ||
| eddn.py | 267 | ZeroMQ commodity/3 listener → market.db |
| eddn_upload.py | 1,105 | Privacy-safe outbound EDDN schema builders |
| spansh.py | 586 | Spansh API client (planners, dumps, station search) |
| updater.py | 533 | Self-update: check, verify, swap, rollback |
| Commander features | ||
| state.py | 612 | Thread-safe live snapshot (AppState) |
| objectives.py | 698 | Objectives + rule-based, time-budgeted session planner |
| operations.py | 633 | Shared ops boards with deterministic JSON merging |
| combatops.py / carrierops.py | 929 | Combat session tracking; fleet-carrier planning & upkeep runway |
| workflowdb.py / specialists.py | 313 | Specialist-workspace storage and glue |
| timings.py | 341 | Per-commander activity timings learned from the journal |
| exobiology.py / biovalues.py | 724 | Exobio predictions, values, first-log estimation, surface map |
| blueprints.py / engineering_catalog.py / engineers.py / wishlist.py | 933 | Engineering planner: recipes, catalog, workshops, pinned wishlist |
| exploration.py | 53 | Exploration payout helpers |
| Platform | ||
| app.py | 227 | Composition root, single-instance guard, lifecycle |
| extensions.py | 717 | Declarative extension host with fingerprint approval + in-app builder backend |
| tts.py | 324 | Piper neural voice (pinned downloads, persistent process) |
| settings.py | 135 | Validated settings store |
Frontend ownership map
| Source rank | Ownership | Enforced boundary |
|---|---|---|
| core/ · data/ | Store, HTTP policy, safe templates, DOM/clipboard/format helpers and pure offline data | No presentation imports; data has no DOM, store or transport work |
| api/ · contracts/ | Twelve named domain clients and explicit request, response and application-state types | Only the core transport calls fetch; public responses use named DTOs |
| features/ | Domain behavior, module-private workspace state, dynamic renderers, static views and idempotent controls | No embedded API paths, direct transport imports or unsafe HTML sinks |
| shell/ | Desktop tabs, Panel pages, shared cards, status, voice and presentation controls | Shares one presentation rank with features while the full graph stays acyclic |
| bootstrap/ · main.js | Composition, polling and initialization order | Named initializers only; bootstrap owns no feature element IDs or listeners |
| styles/ | Tokens, base, components, features, Panel, themes and motion under one cascade entrypoint | Manifested import order, declared layers and 400-line sheet ceiling |
Repository map
frameshift/
├── app.py composition root (headless or windowed)
├── elite/ backend package — 44 modules (table above)
│ └── data/ bundled engineering catalog (gzip)
├── ui/ frontend, served from disk — no build step
│ ├── index.html 21-line document shell
│ ├── panel-bootstrap.js synchronous pre-paint Panel guard
│ ├── hb.js Holo Button interaction helper
│ ├── src/
│ │ ├── main.js ESM entrypoint
│ │ ├── core/ · data/
│ │ ├── api/contracts/
│ │ ├── features/ controls/ · views/ · domain modules
│ │ └── shell/ · bootstrap/
│ ├── styles/ layered CSS graph and single index.css
│ └── fonts/ self-hosted Chakra Petch + IBM Plex Mono
├── tests/
│ ├── test_*.py 55 isolated backend scripts
│ ├── unit/ Vitest modules (31 files total, incl. colocated)
│ └── e2e/ Chromium + WebKit tablet journeys
├── tools/ architecture/CSS/DOM/performance gates,
│ isolated Python runner, served-graph verifier
├── docs/ living UI contract, completed plan, extension docs
├── package.json pinned frontend development and test tooling
├── packaging/ Windows version-resource template
├── .github/workflows/ test.yml (push/PR, 2 OSes) · release.yml (tags)
└── data/ (runtime) market.db · commander.db · security.json
backups/ · tts/ · extensions/ · webview/
Links
- Repository
- github.com/TannerMidd/frameshift — source, releases, issues
- Showcase
- tannermidd.github.io/frameshift — feature tour with live screenshots
- User docs
- Project wiki — getting started, flight panel, trading, autoplot, pairing, security and troubleshooting
- UI architecture
- Living contribution contract — module ownership, request identity, safe rendering and enforced gates
- Modernization record
- Completed implementation plan — final decisions and acceptance criteria delivered in v2.5.0
- Downloads
- Latest release — Windows one-file executable + SHA-256
- This dossier
- Refreshed from the v2.5.0 source tree; figures and counts are measured, not estimated