Gotham console mockup - #65
Merged
Merged
Conversation
…essed The earlier mockups matched Blueprint's palette exactly and still did not read as Gotham, because the gap was never colour. It was structure, type and density, and none of that had been measured. Calibration. Gotham is Blueprint, and Blueprint publishes $pt-button-height: 30px. Both accent-filled buttons in the Object Explorer screenshot measure 19px, so that capture sits at 19/30 = 0.633 of true scale and its 1161px width is an 1834px viewport. Nine further measurements then land on published Blueprint control sizes, which is what confirms the factor rather than assuming it. What that corrected: - Gotham's dense row is 20px, and Blueprint pairs $pt-button-height-smaller: 20px with $pt-font-size-small: 12px. The 12px floor and Gotham density were never in tension; the earlier 26px rows recorded that gap as "deliberate". - The font stack asked for Inter and IBM Plex. Neither is installed, so pages rendered in an accidental Noto Sans fallback. Blueprint names no webfont; it asks for the platform stack, which resolves to Cantarell here (verified via CDP getPlatformFontsForNode, not computed style). - Panels were on dark-gray1. Sampling shows real Gotham is 63.6% dark-gray2, 15.2% black, 3.4% dark-gray1. The surface the operator looks at was one step too dark; every token was individually valid, which is why a palette audit passed it. - The basemap's land fill was 25.4% of all pixels in saturated blue, giving the page a second colour cast fighting the neutral chrome. - Layout used a three-column grid with the map as a middle track, so opening a panel narrowed it. The spec forbids exactly that: panels dock over an edge-to-edge map, nothing owns a grid track. - List rows showed one absolute count and a 15% single-tone bar. Gotham shows a monospace matched/total fraction and a two-tone bar at ~26% of panel width. - The map toolbar was a bare icon strip. Gotham labels its toolbar groups. - Chrome was 22.3% of window height against Gotham's measured 13.3%. Removing the status strip (a Gaia device), folding the event lanes into the transport, and deleting the floating time dock (which duplicated the transport's own scrub track) brought it to 16.7%. Regressions this introduced and fixed: dark-gray3 hover puts --txt-3 at 4.35:1, under the 4.5 floor, so hover is a half-step at 4.67:1; toolbar group containers were unlabelled divs the verifier flagged as unreachable controls. _verify.mjs measures the claims in a real browser rather than asserting them, because two of the same claims were false last time: 0 text below 12px, 0 focusables without an accessible name, 0 keyboard-unreachable controls, every symbol-referencing svg has a viewBox, 0 console errors, and all five structural metrics against the measured reference. Palantir-derived reference crops and the scraped documentation corpus are deliberately excluded from this public repo; README-gotham.md records why and how to restore them locally. Nothing under apps/ has changed. This is a mockup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9Ka6HJykXNE9ToA3fu1yi
…erial
Committed at the operator's explicit request, superseding the earlier decision
to keep tmp/ out of this public repo. What that decision protected against still
applies and is recorded here rather than silently dropped:
tmp/palantir/ the two G-Cloud 14 service definition PDFs and every image
extracted from them. These are public procurement documents,
freely downloadable from the UK Digital Marketplace, but the
UI screenshots inside them are Palantir's copyrighted work.
tmp/redesign/_ref/ Gotham UI crops taken from those PDFs.
tmp/redesign/_corpus/ 115 pages / 34,119 words of Palantir's published
Gotham documentation, scraped by _scrape.py.
.gitignore still lists tmp/ and Palantir_Gotham_*.pdf; these paths are tracked
by force-add, so the ignore rules no longer apply to them.
Scanned before committing: no API keys, private keys, .env files or
credential-shaped strings. The token/bearer matches are prose in Palantir's own
authentication documentation.
Excluded as build artefacts, not as policy: tmp/redesign/node_modules (14 MB,
playwright-core) and tmp/redesign/__pycache__.
Also lands the three direction mockups, the panel and parity galleries, the
rehoming inventory and the states sheet, which the handoff document warned were
one `git clean` from being lost the same way tmp/mock.css and tmp/power.css were
while still cited at shell/instruments.tsx:4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9Ka6HJykXNE9ToA3fu1yi
Several upstreams answer this backend with 403/451 rather than a key error, and the only tool the repo had for that was an UPSTREAM_PROXIES pool that ships empty because there is nothing free to point it at. Add two tiers, both off by default, and — more usefully — the measurements that say which one a given host actually needs. Cloudflare WARP (app/warp.py, scripts/warp.sh): `warp-cli mode proxy` makes the daemon publish a SOCKS5 proxy on loopback whose exit is a Cloudflare consumer address. The consumer free tier registers anonymously, so there is no account, no key and no login; host routing and DNS are untouched and nothing needs root. Traffic through it resolves at the exit on Cloudflare's resolver. app/upstream.py mounts it per host, or for everything via WARP_ALL, with a kill switch that removes the direct fallback. The tier is supervised and surfaced in /api/status, which also finally exposes proxy_stats(). WARP_HOSTS ships EMPTY on purpose. Measured from this host (tools/probe_warp.py): WARP unblocked nothing, left every already-working feed unchanged, and made OpenSky unreachable. The 451/403s this was built for were measured from the datacenter deployment; re-run the probe there before listing a host. Three different things look like a Cloudflare block and only one is: - A WAF path rule. globe.airplanes.live/data/aircraft.json is 403 to httpx, to curl with a full Chrome header set, through WARP, and to real Chrome that had just loaded the site. Nothing opens it; read the page's own request via /fetch?url=<page>&capture=<regex>. - Not blocked at all. The endpoint that page uses, /re-api/?binCraft, answers bare httpx with 200. The browser's value there is discovery, not bypass. This contradicts the standing "re-api is Cloudflare-403" note in config.py, which was taken from the droplet; flagged there rather than acted on. - A real managed challenge. globe.adsb.fi serves "Just a moment"; httpx cannot pass it and neither can headless Chrome, which sat on it for 25s. Headful Chrome under xvfb-run got 200 and the real page from the same address. So the browser tier (tools/browser-fetch, app/browser_fetch.py) runs real Chrome with a per-host cookie profile, no UA override, one page load at a time per host on a jittered floor, and browser_headful for the challenge case. Headful never touches the operator's session: xvfb-run -a takes its own display and --ozone-platform=x11 is pinned, because WAYLAND_DISPLAY is inherited even inside xvfb-run and a Chrome that preferred Wayland would draw on the real desktop. stop() takes the process group; measured no orphaned Chrome or Xvfb. Guards: tests/test_warp.py, tests/test_browser_fetch.py, and a node selftest for the pace gate and the x11 pin, wired into scripts/verify.sh. Baseline 2141 -> 2162.
app/profile.py documents setdefault semantics: "anything set explicitly in the environment or .env still wins". The .env half was not true. pydantic-settings ranks real environment variables ABOVE .env, so seeding a profile default into os.environ does not lose to an .env entry, it silently outranks it. Measured on a 32-core / 121 GB / RTX 5090 box: .env carried ADSB_SIDECAR_ONLY=1 and the operator added ADSB_SIDECAR_ENABLED=1, and the backend still booted with :8090 never spawned. OSINT_PROFILE was unset, so the profile resolved to lite, lite seeded ADSB_SIDECAR_ENABLED=0 into the environment, and main.py gates both start() and supervise() on that flag. The tier left no trace at all (zero sidecar lines in 3.6 MB of log) and the snapshot ran on its sub-8000 backfill while sidecar-only mode was still set. apply() now treats a key present in either .env as operator-configured and leaves it alone. The existing "an explicit setting always beats the profile" test passed throughout, because it only ever used monkeypatch.setenv — a real environment variable, the half that already worked. Add the .env case, plus one for comment and blank-line parsing. The autouse fixture now chdirs to a tmp directory: with apply() reading .env, every assertion in that file would otherwise depend on whatever the developer has in the repo root, green on one box and red on another. Baseline 2162 -> 2164.
The root CLAUDE.md had grown into one long list covering the globe, the backend feeds, the sidecars, the venvs and the deploy scripts at once, so anyone editing a single directory had to read all of it to find the two rules that applied to them, and in practice skipped it. Split it per directory: each of apps/api, apps/web, apps/ml, apps/desktop, packages/shared, tools, scripts and infra now states the rules that govern it and names the operator decision behind each one. The root file keeps the operating method, the environment traps and a table pointing at the rest. The guards are unchanged; scripts/verify.sh still runs all of them.
The two previous mockup sets this codebase was built from lived in tmp/ and were deleted; shell/instruments.tsx still cites tmp/mock.css and tmp/power.css, neither of which exists. That is the reason a rebuild has to re-derive the grammar from screenshots every time. This is the durable copy: the twenty-odd pages the current shell was measured against, their generators, the reference shots, and WIRING.md, which maps every one of the old rail surfaces to where it lives now and carries the status of each. COVERAGE.md and FRAMES.md record what was compared and how. The shots are 12 MB. That is the price of not doing this a fourth time.
The new shell landed with four named left panels, a right dock and one app
launcher, and a panels.ts recording where each of the 27 old rail surfaces
went. The record was accurate and the wiring was not: byHome() only keeps
entries of kind 'panel', so the eleven surfaces re-homed as map tools, as app
tabs or as window chrome were constructed on every render and thrown away.
The contract test passed either way, because it checked the record rather than
the code.
Each now lives at the address panels.ts gives it:
all sources Layers panel, curated / all switch
annotate, floating over the map, opened from GlobeToolbar
watch, field
imagery Video app, Imagery tab
extract Investigate app, Extract tab
countries Country app, National sources tab (a source catalogue is
not the same thing as a country dossier)
answers AI app, above the Watch Officer
inbox title bar, beside the alerts bell
shell/rehoming.test.ts names the file that must render each one and fails if
an address goes empty. It also checks both halves of the old Ops panel survive:
AOI watch came across in the first pass and the standing-detection rollup did
not, which lost the level view of what is currently tripping the operator's
rules. Info carries it again, polling /api/alerts/standing.
Selection was the other half of the same problem. It had the new section
grammar and none of the dossier, so the assessment, the actions, the
connections graph, the imagery, the pattern of life, the raw property bag and
every place-specific card were gone. It now composes the existing cards rather
than reimplementing them, and routes situation: and domain:-style ids to their
own panels as the old inspector did. Three defects found against live contacts:
identity ignored the resolved enrichment and printed "Type -" beside an
enrichment card that knew the type; one aircraft-shaped identity block rendered
for every kind, so a quake showed six rows of dashes; and freshness read
seen_pos_s, which is ADS-B only, so every vessel reported no last fix.
The title bar's File / Edit / View / Collect / Window / Help set a menu state
that nothing read - no dropdown was ever rendered. They carry real commands
now, each running against the store that owns it, with disabled items stating
why in a tooltip. Settings had the same shape of bug from the other side: the
modal is hosted in AppRouter's TopBar, which returns null on the console route,
so the button had nothing to open. The palette moved from useState inside
Omnibar to its own store, since a command palette only its own keystroke can
reach is the problem it exists to solve.
The centre of the bar was a hardcoded amber square, a literal "Live map" and a
green check reading "Saved", on a console that saves nothing and is often not
showing the map. It reads the active app, the real clock, live or held, and the
alert socket, each from the store that owns it.
Find regains the object-type filter and the rolling time window it had before
the rebuild, and can save a search as an Inbox subscription. The four-region
compositor is not carried over; WIRING.md says so and why.
verify.sh green: 2164 backend passed + 2 skipped, 549 web tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.