From 721bce1019b8db72ea9902641ca682393f747027 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sun, 23 Aug 2026 17:45:57 -0400 Subject: [PATCH 1/6] =?UTF-8?q?docs(design):=20the=20web=20UI=20rewrite=20?= =?UTF-8?q?=E2=80=94=20zero-dependency,=20view-source,=20on-device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the design for replacing render.py's server-side HTML with a JSON API plus plain ES modules, following youperiod.app's technical philosophy. The three findings that shaped it: - Installing the app FROM keeltrading.com cannot work. keel's data is a local SQLite file; an HTTPS page cannot reach 127.0.0.1 reliably, and the exits are a tunnel or a hosted service — both excluded. The PWA is therefore served by keel itself, where localhost is already a secure context. - keel's docs/ is the SOURCE, not a duplicate: keeltrading.com fetches it at build time and fails loudly if a pinned document vanishes. What ships is the app code dropping doc prose and deep-linking instead — which also fixes an already-shipping bug, since the wheel carries no docs/ and /glossary renders an empty state in every installed deployment. - --good and --bad have the same relative luminance in light mode (1.01:1), so profit and loss are separated by hue alone. Fails WCAG 1.4.1 in a trading app. Contrast becomes a CI assertion, not a design review. Zero third-party JS is achievable because crypto lives in Python and money crosses the wire as pre-formatted strings, so the client never computes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NyeggYtojNXCTHeD3JHxb6 --- .../specs/2026-08-23-web-ui-rewrite-design.md | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md diff --git a/docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md b/docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md new file mode 100644 index 0000000..4ee3a66 --- /dev/null +++ b/docs/superpowers/specs/2026-08-23-web-ui-rewrite-design.md @@ -0,0 +1,287 @@ +# Design — The web UI rewrite: a zero-dependency, view-source console + +**Status:** proposal · **Date:** 2026-08-23 · **Supersedes:** the HTML-rendering half of D2 (#435) + +## The ask + +> "Rewrite Keel's UI to be used on mobile, desktop, laptop, or tablet. Easy to navigate for simple +> and newbie users. Strip out all documents from Keel and point the new UI to keeltrading.com +> documents. Technically I propose PWA to avoid stores' fees and the app will be installed from +> keeltrading.com." + +Refined across the design conversation to: **keel stays entirely on the user's device, open source +and free, no server reach.** A paid analysis service is explicitly a *different app and repository* +and is out of scope here. The technical philosophy to follow is +[youperiod.app's](https://github.com/getify/youperiod.app/discussions/36). + +## The answer in one paragraph + +Most of this already shipped. `keel serve` (#435) binds `127.0.0.1:8765`, mints a session token and +renders eight routes; D1, D3, D5 and D6 are closed and only D4 (#437, the first-run wizard) is open. +What is left is one layer: **`keel/web/render.py`'s 872 lines of server-side HTML generation get +deleted, and the local process serves JSON plus static files instead.** The client becomes plain ES +modules — no framework, no bundler, no transpile, no minification, no source maps, and *zero* +third-party JavaScript. Two things make that last claim achievable rather than aspirational: keel's +cryptography lives in Python and the OS keychain, never in the browser (so youperiod's three +dependencies have no analogue here), and **money crosses the wire as pre-formatted strings**, so the +client never performs arithmetic and never needs a decimal library. The one part of the original ask +that cannot be built is installing the app *from* keeltrading.com — that is an origin problem, not a +preference, and §3 records why. + +--- + +## 1. What exists today + +| component | lines | state | +|---|---|---| +| `keel/commands/serve.py` | 89 | binds loopback, mints a one-time token, opens the browser | +| `keel/web/server.py` | 639 | 8 GET routes + a POST write surface at `/setup/` (#469) | +| `keel/web/render.py` | 872 | server-side HTML, inline stylesheet, no JavaScript | +| `keel/web/security.py` | 164 | session token | +| `keel/commands/tui.py` | 5,063 | curses TUI (+ 4,369 lines of tests) | + +Routes: `/`, `/setup`, `/activity`, `/insights`, `/rules`, `/venues`, `/gates`, `/glossary`. + +D-series status: **D1 #434 · D2 #435 · D3 #436 · D5 #438 · D6 #439 all closed. D4 #437 open.** + +`render.py`'s own docstring states the invariant this design must preserve: + +> "This module is a THIRD renderer over the same reports, never a second place that computes them." + +`tests/commands/test_console_thinness.py` pins that property today. It must pin the API layer after. + +## 2. The reference, and what actually transfers + +youperiod.app is 137 KB total. Its `server.js` is 4,865 bytes and performs **no rendering** — its +entire job is serving static files with correct security headers. The client is ~35 KB of +hand-written ES modules across eight files, one 3.9 KB stylesheet, no build step. + +**Transfers directly:** the no-framework rule; the "third-party code needs strong justification, not +preference" bar; no transpiling, bundling, minifying, or source maps; security headers as the +server's main job; offline, performant and accessible by default. + +**Does not transfer:** youperiod is client-only because it has *no engine*. Its data is what the user +typed; the browser is the app; IndexedDB is the database. keel is the inverse — a Python process +wakes on a schedule, talks to the venue, enforces the rails and writes SQLite **with no browser +open.** IndexedDB therefore cannot be keel's store of record. + +The *guarantee*, however, does transfer intact. youperiod's "no stateful server" means your data +never leaves your device. keel's equivalent is **the server is on your device.** + +**Explicitly rejected:** youperiod encrypts everything at rest behind a 12-character passphrase. +keel cannot. The agent runs unattended via launchd; a passphrase-locked database is one nobody can +open at 03:00. Passphrase-at-rest and unattended autonomy are mutually exclusive, and autonomy is the +deliberate choice. The correct analogue is D4's plan: **OS keychain for credentials**, filesystem +permissions for the database, FileVault/BitLocker for at-rest. + +## 3. Why the app cannot be installed from keeltrading.com + +keel's data is a SQLite file on the operator's machine, written by a local process holding venue +credentials. A page served from `https://keeltrading.com` is a **different origin** and cannot read +it. It could only fetch it from the local server, and an HTTPS page fetching `http://127.0.0.1:8765` +is precisely the path browsers are tightening — Private Network Access preflights in Chrome, blocked +outright in Safari. It is inconsistent today and narrowing. + +The only two exits are giving the local server a real TLS certificate (requires a tunnel — excluded +by "no server reach"), or moving the data to a hosted service (a different product, explicitly out of +scope). + +**Therefore: the PWA is served by the origin that serves the data, which is keel itself.** +`http://127.0.0.1` **is** a secure context by specification, so a service worker, a web app manifest +and browser install all work there with no networking decision at all. keeltrading.com hosts the +install instructions and the documentation — which is what it already does. + +**Consequence, recorded plainly: iPhone and Android are out of scope.** Not deferred by choice — +excluded by the constraint. There is no keel on iOS, and "no server reach" removes the tunnel that +was the only way a phone could have reached the desktop process. The UI is still built responsive, +because desktop windows get dragged narrow, touch devices running a full OS are in scope, and it +costs almost nothing while the CSS is being written anyway. + +## 4. Architecture + +### 4.1 Process model + +``` +launchd ──► keel agent (daily) ──► SQLite ◄── keel serve ──► 127.0.0.1:8765 + │ + ┌──────────────────────┴───────────┐ + │ GET / static assets │ + │ GET /api/* JSON │ + │ POST /api/* gated actions │ + └──────────────────────────────────┘ +``` + +Static assets and the API share **one origin**, so there is no CORS story at all. Do not split them. + +### 4.2 The API contract + +The API is a *fourth* consumer of the same frozen report dataclasses the console and the TUI already +consume. It never computes; it serialises what `gather_status`, `build_insights_report` and their +siblings already return. + +**Money crosses the wire as strings, never as JSON numbers.** `JSON.parse` yields IEEE-754 doubles, +and keel is `Decimal`-only for exactly the reason that matters here. A serialisation test fails the +build if any monetary field emits a JSON number. + +**The API emits presentation-ready values.** Not `{"qty": "0.01", "price": "50000"}` for the client +to multiply — `{"notional": "500.00", "notional_display": "$500.00"}`. Every figure a user sees was +computed by the Python that holds the rails. This is what lets `test_console_thinness.py` extend to +the API layer, and it is what makes §4.3's zero-dependency claim possible. + +### 4.3 The client + +Plain ES modules, served as authored. **No framework, no bundler, no transpile, no minification, no +source maps** — per §4 of the reference philosophy, the code running must be byte-identical to the +code the user reads in devtools. gzip is fine. + +**Zero third-party JavaScript.** Applying the reference's own bar — a dependency must be justified +because getting it wrong ourselves would undermine the project's principles — nothing clears it: + +| need | answer | +|---|---| +| charts | SVG polyline, hand-rolled (~150 lines) | +| live updates | `EventSource` (SSE) | +| dates | `Intl.DateTimeFormat` | +| routing | History API over one shell | +| decimal arithmetic | **none required** — §4.2 | +| cryptography | Python + OS keychain, never the browser | + +**Type checking without a build step:** `// @ts-check` with JSDoc annotations and `tsc --noEmit` in +CI. Types are checked; nothing is transpiled and nothing shipped is altered. + +### 4.4 Security headers + +The server's principal job, as in the reference. + +- `default-src 'self'` — no CDN, no web fonts, no analytics, no third-party anything. +- **`connect-src 'self'`** — the UI is *provably incapable* of sending positions, equity or trade + history to any origin but the local process. Browser-enforced, one header, verifiable in seconds. + This is a stronger claim than keel can make today and should be documented as a feature. +- `X-Content-Type-Options: nosniff`; Subresource Integrity on any inline `