Universe Glam: the pink brand system, and the gates that keep its roles apart - #7
Merged
Merged
Conversation
Enabling the database made the backend exit at startup. updatePoolsJson fetches the pools-v2.json sha from GitHub, that fetch must not happen at runtime here, so currentSha stayed null and index.ts aborted before it could serve anything. Bundle the pool list in the repository and identify it by the git blob hash of its own bytes. That is deterministic, needs no network, and is directly comparable with the sha already stored in the state table, so a redeploy with unchanged pool data does no work. The bundled file is the default now; the GitHub URLs stay for anyone who wants them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rent one The frontend decided on its own that Mining and Charts were available. The backend decided separately, from DATABASE.ENABLED and INDEXING_BLOCKS_AMOUNT, whether to mount the routes behind them. Nothing compared the two answers, so production shipped both pages against routes that were never registered and every request behind them answered 404. Give the two one place to agree on. The route setup records what it actually mounted, /api/v1/capabilities reports that alongside dependency reachability, coverage and lag, and startup refuses a configuration that would advertise a feature it cannot serve. The rules live in their own module with no imports, so the release gate and the tests judge exactly what the running backend judges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every $listXX range caught its database error, logged it, and returned an empty array. The route then answered 200 with [], so a database that was down was indistinguishable from a chain with nothing to show, and the Charts page rendered an empty state over a real outage. Throw instead. statistics.routes already turns a thrown error into 500, which is the answer the frontend needs in order to tell unavailable apart from empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Charts page subscribed with one next handler and no error handler. A 404 killed the subscription, so isLoading stayed true, the spinner stayed on screen, and the range buttons stopped working for the rest of the session. Every mining widget had the same shape through an async pipe with an else skeleton, so a failed read left 35 skeletons that nothing would ever clear. Add one helper that turns a request into a state machine which always reaches a terminal state: data, empty, stale, or error. It keeps empty apart from failed, bounds the wait, retries only what a retry could fix with backoff and jitter, cancels an obsolete request when the range changes, and keeps the last good answer to show as stale rather than replacing real numbers with a blank panel. Charts, reward stats, pool ranking and the hashrate chart use it. Each owns its own state, so one failing module no longer decides what the rest of the page shows. The shared status panel names what failed and offers retry and system status, and its spinner is drawn from theme tokens: the one it replaces was white on a light background, which is why a failed page and a blank page looked the same. Pool ranking and the hashrate chart also started one request from route setup and another from the chain tip. They share one trigger now, debounced so a burst of tips around a new block is one refresh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last release passed every check and still shipped a Charts page and a Mining dashboard whose backend routes were never mounted. CI answered from fixtures, so nothing it ran could have noticed: the thing that was wrong was the configuration of the machine, and no check looked at that. release.sh installs beside the running release and refuses the symlink swap unless the build is complete, the configuration is coherent, the database answers, the source registry parses with every token present, and every protocol the registry calls readable has an authority configured. After the swap it reads /api/v1/capabilities and fails the release if a feature is enabled with no routes behind it, which is exactly the state that shipped. The integration database is pinned to the engine and major version the deployment runs. It was MariaDB 10.5 against a MySQL 8.4 plan; the migrations use MariaDB syntax MySQL rejects outright, so a test database on the other engine would have proved nothing about the release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The product shipped an ultramarine identity that belonged to no other Bitcoin Universe surface. Core, Wallet and Inscribe are all built on the same pink-forward system, so the explorer read as a different company's product wearing our name. This replaces the brand layer rather than recolouring it. Three roles now exist where one did: a working pink that is readable both as text and as a fill, the #ff0066 identity anchor for borders, rings and display type where it carries no small label, and the lavender that carries everything the brand pink should not, including focus. Surfaces move to pearl on light and to the plum-black the rest of Universe uses on dark. Nothing pink means anything. The five evidence states keep the conventional green, amber, blue and red, protocol identity keeps its own scale, and the fee scale is untouched, because a fee band means a fee rate. Every value here was derived against the contrast module rather than picked: the anchor is 3.85:1 on white, which is why it is not a button fill, and the chart series were solved for separation under protanopia, deuteranopia and tritanopia as well as normal vision. The gate grew to match. It now measures that the brand is never a state or a protocol, that two protocols are never the same colour, that dark and high contrast declare every token light does, and that the retired ultramarine values cannot come back. Adding it found three collisions that predate this change: ordinals orange sat 11.1 dE from the amber meaning partial evidence, and stamps and op data sat inside the new brand family. 331 palette pairings checked, 0 failing.
The page put two chips of equal weight beside each protocol: what the registry says it implements, and what the runtime snapshot says its authority can do. So Ordinals, Rare Sats and Runes read "Live, read only" next to "Authority unreachable", and the summary counted all three among the protocols readable that day. Both facts were true; neither was the answer to the question a reader is asking. Availability now decides the primary label and the count. A protocol whose authority is unreachable, unconfigured, or still catching up is not readable, whatever the registry says it implements. The registry capability follows as a qualifier, and a line underneath gives the evidence: where the authority has reached, how far behind that is, how many checks have failed in a row, and when it was last asked. An authority that is catching up gets its own state rather than being rounded to working or broken, because that is what an index rebuild actually is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ing zero A currency with no rate fell through to a multiplier of zero, so every amount rendered as a confident $0.00. That is the same error as answering a failed query with an empty list: it turns "we do not know" into a definite value, and a reader has no way to tell the two apart. With no usable rate the amount is left blank and titled. This surfaced when the third-party price feed was switched off, but the fallback was always there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…the docs Search is the product's dominant action and now looks like it: the brand sweep, a top gloss, and a soft glow, on the one control allowed to carry them. Hover moves brightness rather than swapping to a second gradient, so the measured relationship between the fill and its label survives. The gloss is 0.16 rather than the 0.24 that looked better. At 0.24 a white label falls to 4.06:1 on the violet end of the sweep, which the gate now measures: the label is checked against every gradient stop, through the gloss layer, in all three themes. The research this came from is recorded rather than asserted. A visual language pass read each competitor's declared shell colour out of its own markup and found the category uniform: mempool.space at #1d1f31, Blockchain.com black, Blockstream dark, and a documented convention of low chroma and cool temperature. Light, warm and chromatic is an empty position, and the pass checked whether it is empty for a good reason before taking it. Two further passes on different strategies produced no new competitor, capability class, or trend, which is the closure condition. 343 palette pairings checked, 0 failing.
The check rendered LF and read a working copy that this Windows host checks out as CRLF, so it reported a clean tree as stale and could only ever pass on the Linux runner. Competitor measurements move to the research doc, where the branding and origin gates already expect to find a competitor named.
The visual matrix checked overflow, console errors, contrast and accessibility. A Charts page that never resolved and a Mining dashboard of 35 skeletons passed all four and shipped: nothing overflowed, nothing logged, placeholders have fine contrast, and axe is content with a skeleton. The matrix now measures whether a page finished. With a populated fixture there is no excuse for a loader still on screen after the settle wait, for a skeleton that never resolved, or for a chart panel that drew nothing, and each of those fails the run. With a failure fixture the obligation is the opposite: a page still waiting must have said why. Fixtures still cannot prove the deployment can produce data at all, so synthetic-check.mjs asks the live origin directly. It fails on a feature advertised with no routes behind it, a range that answers with nothing, a protocol marked readable whose authority cannot answer, a configured authority with no checkpoint, and a frontend and backend on different builds. It runs after a release and hourly. The backend integration tests now run in CI too, against a real database, because the migration failure that had to be found by hand was a database one and no test in the suite touched a database. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The deployment notes described a database that was off, and the release procedure was a list of manual steps with no gates. Both are now what the machine does: a dedicated MariaDB container, why it is MariaDB and not the MySQL pinned elsewhere, the bounded indexing window and why it is bounded, the bundled pool metadata, the price feed that is off and why, and a release that refuses to cut over on a configuration it cannot serve. The overlay ADR gains the status contract. Capability and availability were never written down as separate things, which is how they came to be rendered side by side with equal weight and read as one answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… a reason Three defects the pink found. A loud colour is useful that way: it shows you where the hierarchy was already broken. The range selector painted all eleven periods as filled primary buttons and marked the chosen one with .active alone, so nothing on screen answered "which period am I looking at". Inside a group of alternatives the unpicked options now go quiet and the fill means "this is the one". Standalone primary buttons are untouched, because those are actions rather than answers. This also fixes the Mempool and Mining switcher and the RBF filter. The label above a number on a stat card took the brand colour, on 45 of them, which made the mining dashboard nine pink labels competing with the figures they introduce. Brand marks identity and intent; a label is neither. The largest mining pool was drawn in #D81B60, 6.7 dE from the brand pink, so a pool's identity wore the product's colour. That value and #880E4F are now held back from the pool ramp alongside the yellow already reserved for the unknown pool. Two more things the review turned up. The chart zoom slider was drawn from the charting library's defaults, a pale blue measuring about 1.15:1 against the page, on eighteen charts; it is themed once now and spread from there. And the error detail under a failed panel read "(undefined undefined: )" for any failure that was not an HTTP response, because the pipe read .status off whatever it was handed and its null guard came after the property access. It now answers with something actionable or with nothing. 154 frontend tests pass, 343 palette pairings, 0 failing.
The capability probes and the bundled pool import each catch their own errors and return a value rather than throwing, so awaiting them is safe. The lint rule cannot see that without the annotation, and it was right to ask: the rollback in the pool import was itself an unguarded await, so a failing rollback would have replaced the error that caused it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mempool depth chart drew its fee bands from the inherited categorical ramp, which is a rainbow. Applying a categorical palette to an ordered quantity means the colour tells a reader nothing about whether a band is cheap or expensive, and one of its bands sat 6.7 dE from the brand pink. Blocks and the Lens already read this product's fee scale. The depth chart and its legend now read the same one, so a fee rate has a single colour everywhere: green for cheap, through amber, to deep magenta for the most expensive band. The scale is a TypeScript array rather than a custom property, so the theme is detected from a variable only the high contrast theme declares, and the answer is cached and dropped on a theme change like the rest of the chart chrome. None of this was visible before, because the visual QA fixture reported a mempool of roughly eighteen thousand vBytes, four orders of magnitude below a real one. Every y axis label rounded to "0 MvB" and the bands were too small to resolve, so the most colour-dense surface in the product rendered as one flat area and its palette was never actually reviewed. The shape is unchanged; only the scale is real now. One pairing in the product does not clear the 25 dE the brand is held to everywhere else: the top of the fee scale sits 10.7 dE from the light brand fill. Both values are load bearing and neither can move, so it is recorded in the gate at the distance it actually has. Nudging either one closer now fails the build. 346 palette pairings, 0 failing. 154 frontend tests.
A gate that never fires is indistinguishable from no gate. The judgment is exported and covered directly: a spinner still turning on a populated page, skeletons that never resolved, a chart panel that drew nothing, a page that is only placeholders, and a failure state that waits without saying why all fail; a page that finished and a failure state that explains itself do not. Importing the harness no longer launches it, so the test can reach the judgment without driving a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The clock link, the fee-level filter, and the invert toggle are icons with a title and no accessible name, so a keyboard or screen-reader user reaches three stops that announce nothing. The keyboard harness had been reporting all three; nothing acted on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI runs it hourly, which only covers the window CI happens to be awake. A timer on the machine that serves the origin covers the rest, writes to the journal, and marks the unit failed when a check fails, so an outage between releases is visible where every other service failure already is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fill rule The source page exists to say which commit is running. It asks for that over REST, and that route had no fixture, so the one page whose whole job is to publish the release rendered with an empty release and an empty backend in every screenshot of the matrix. Nobody reviewing it could have seen whether it works. The design system now also states the distinction the review kept running into: filling a bar is not carrying a quantity. A progress bar or a single-series area may be brand pink, because the length carries the value and the fill is only the mark. Colour that encodes the value stays off limits. If changing the number would change the colour, the colour cannot be the brand.
Fulcrum is gone from the explorer host: no process, no data directory, no unit file. The backend was still configured to reach it, and a dead address backend does not fail loudly, it retries. It was producing roughly two connection errors a second, forever, which buried every real error in the journal. Core answers blocks, transactions and the mempool either way, so the deployment reads addresses from Core for now and an address lookup fails immediately rather than hanging. The preflight refuses an electrum backend when nothing is listening on its port, so this cannot come back quietly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s room Two pages carried a 39 KB decorative block graphic beside their heading, one on the blocks list and one on the transaction test page. Both were drawn for a dark shell with colours from no palette in this product: navy, charcoal and an amber gradient sitting on a pearl page. They carry no information, and a floating decorative shape is the thing this design system exists to avoid. Removing them also removes 39 of the 93 colour literals the branding allowlist was carrying, and takes the same weight out of the sprite sheet that ships in the main bundle. The block size bar's label is positioned out of flow, so it landed on whatever followed it. In the blocks table that was the next row, and the figure was cut in half. The reserve is made only where a label exists.
Five rules used --info, which resolves to the evidence state that means "informational", as a generic accent. That is what the theme this product grew from did, because that theme was blue and the accent happened to match. Under a pink brand they were the only cool text left, and the API docs page in particular rendered every endpoint header in the colour that means a status everywhere else. An endpoint header and a collapsed disclosure are controls, so they take the brand. A subtitle and a code label are structure, so they take secondary text. A hover is interaction, so it takes the brand. The three genuine uses stay: the duplicate transaction alert, the informational badge, and the proof widget's active marker are all actually informational.
…at it says The new visual gate found this on its first real run: with every request held open, the protocols page sat on its skeleton indefinitely. The registry read had a catchError but no deadline, so a request that hangs rather than fails left the page waiting with nothing subscribed to clear it. It has a budget now, and the error state it reaches offers a retry. The gate's own rule was also wrong for the fixture named loading, which holds requests open on purpose to photograph the waiting state. Asking that to have finished asks the wrong question. It is judged on whether the wait is announced at all, which is what a screen reader needs and what a blank rectangle fails; the deadline itself is covered by the lifecycle tests, which run far longer than the harness waits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A run pointed at a second output directory committed 21 screenshots and its report, because only the default directory was ignored. Ignore any output directory the harness is pointed at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Statistics are collected from the moment the writer starts, and nothing backfills them: there is no first-party source to backfill from, and inventing rows would be worse than having none. So a 1W range currently draws a couple of hours of samples under a heading that says 1W. That is not a lie the chart tells on purpose, but it is one a reader would take away. When the samples cover noticeably less than the range asked for, the page says when collection began and that this is all the history there is. The note disappears on its own as the history fills in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The history note read "1 hours ago", and it was a getter, so it reduced over the whole series on every change detection pass, on a page that takes a live sample every minute. It is computed when the series changes now, and it counts in singulars. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The matrix covers three themes, seven widths, and six data states. It does not cover forced colours or 200 percent zoom, and both are things this product claims to support. Forced colours is the interesting one, because the brand does not survive it and is not supposed to. The operating system replaces the palette wholesale, so the question is whether the interface still works once every colour decision in this repository has been overruled: are controls still bounded, is text still present, is anything painted on itself. That is a different failure mode from low contrast and nothing here was looking for it. Both hold on every route checked, with no horizontal scrolling at 200 percent, which at a 1280 viewport is a 640 pixel layout. The socket is not mocked in this check, so the chain strip shows placeholders and the header reports itself offline. That is stated in the file rather than left for someone to rediscover: the questions it asks are answered by page structure, and the data surfaces are the matrix's job.
…it hide The mining dashboard's hashrate and difficulty panels had no fixture, so half of one of the thirteen reviewed routes rendered as skeletons and a spinner in every screenshot the matrix has ever taken. Giving them data made the panel render, and the panel immediately failed: the difficulty change figure is #42B747 on the light page, which measures 2.37:1 against the 4.5:1 it owes. The colour is set through an Angular property binding, [style]="up ? 'color: #42B747' : 'color: #B74242'". The colour gate's markup pattern required a literal style attribute, so a binding that paints exactly the same way was invisible to it, and had been for as long as the gate has existed. The pattern now matches [style], [ngStyle] and [attr.*] too. Green still means up and red still means down. They are the evidence state tokens now, which are the versions that clear the floor on every surface. Mining now reports 0 contrast failures and 0 accessibility violations across light, dark and high contrast, where before it reported nothing at all.
A restore was verified by hand, which proves the dump format works and nothing else: there was no backup being taken. A daily timer now writes to the same directory and keeps two weeks. It refuses to keep a dump that is suspiciously small and checks the archive reads back, because a backup nobody verifies is not a backup. The deployment notes gain the measured growth figures, so the next person deciding whether to prune or to widen the indexing window has the numbers rather than a guess. At roughly 300 MB a year against 1.5 TB free, nothing is pruned, and statistics are kept indefinitely on purpose: the "all" range is the whole series. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notes still told the reader to compose a dump command by hand, which was accurate before there was a timer taking one every day. They now name the unit, and they say to verify a restore rather than to trust that a dump exists, because that is the part that was actually missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… exist check-palettes proves the token pairs are readable: white on the brand fill, near black on the dark theme's brand. It cannot see whether a component actually uses them together, and a rule that paints a brand background and says nothing about colour inherits the page ink, which on light is near black on hot pink. That is the exact failure the brand and contrast tokens were split to prevent. Five surfaces had it: the accelerate button and the four menu tier badges. None of them render in this deployment, because both features are disabled here, so no screenshot could ever have shown it and no reviewer could have caught it. They would have been wrong the day someone turned them on. The check only looks at rules that also lay out text, so a dot, a bar, a rule and a toggle track are correctly left alone. 62 fills checked, 0 failing.
The step failed in CI on its first run: a container left behind by an earlier run was still up, so compose reported it as already running and never recreated it. The engine had changed underneath it, and the readiness probe then waited out its attempts on a database that was never going to be the one the tests asked for. Tear the previous container down, volumes included, and recreate. Remove a stray container holding the same name too, because `down` only removes what compose owns and `up` fails on the conflict rather than starting anything. Probe with either client binary name, since MariaDB renamed them and a probe that knows one name reports a healthy server as a database that never started. Verified by leaving a container of the wrong engine running and watching the suite replace it and pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The em dash gate scanned the repository and skipped the build output, which is the half a reader actually sees. Copy reaches the bundle from templates, from generated configuration and from metadata, so a clean source tree was never proof of a clean page. Pointed at frontend/dist it passes today, with one exemption: the third-party licence notice reproduces every dependency's licence exactly as its authors wrote it, and editing somebody's licence to satisfy our punctuation is not an option. It is the only file in the output carrying the character.
…nshot The fixture pinned only the address summary. The harness falls back to a prefix match, so /api/address/<addr>/txs matched the summary's key and was answered with the summary object. The page called forEach on it and threw. One of the thirteen reviewed routes has been rendering its error state in every screenshot the matrix has ever taken, and the error it rendered was the "(undefined undefined: )" this branch already fixed. Pinning the sub-routes made the page render, and the page immediately failed contrast: the negative balance button is .btn-danger, which the Bootstrap bridge never restated, so it kept the framework's own #dc3545 and inherited the page ink at 3.21:1. .btn-warning and .btn-info had the same gap. All three now take the state tokens and declare their ink, which also fixes the stale block badge, the unconfirmed count, and the replaced and removed states. check-fills cannot see this class: the fill comes from compiled framework CSS rather than from a token in our own stylesheets. Measuring rendered pixels is what caught it, and that only works when the page renders. Address, block and transaction now report 0 contrast failures and 0 accessibility violations across light, dark and high contrast.
The production monitor recorded the last deploy as an outage: three checks failed at the exact second of the cutover. The backend and the overlay take a few seconds to listen again after a restart, and the gateway answered 502 the instant the connection was refused, so every request in that window became a visible failure. A request with no body is now retried over about five seconds while the connection is refused. That bridges a restart, and it never retries a write, because only GET and HEAD can be replayed. An upstream that is genuinely gone still gets a 502, still well inside the page's own budget, so a real outage is reported rather than hidden behind a long wait. Verified by starting the gateway against an upstream that is not listening and bringing it up a second later: 200 after 1.8s, against 502 after 5.3s when nothing comes back at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Probing the origin through a cutover still caught two 502s. The retry added for a restarting upstream cannot help there: the gateway itself was being restarted, and nothing behind a process that is down can answer for it. It does not need restarting for most releases. The backend and the overlay run from a path baked into their unit at exec time, so they have to come back; the gateway resolves its static root per request, so a new frontend reaches it through the symlink on the next request. It restarts only when its own file differs from the running release, which is the one case that still shows a brief gap, and the log says so when it happens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notes said a deploy caused at most a brief connection reset, which was a guess. It has been measured now: probing the origin once a second through a cutover returned 200 for every request. The one case that still shows a gap is a release that changes the gateway itself, and the notes say which case that is rather than leaving the reader to find out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It read the page with an evaluate that could be in flight when the router redirected, which destroys the execution context underneath it. A race, so it passed here every time and failed on the runner. Zoom is applied through an init script now, so it lands on every document as it is created and nothing has to be evaluated after a navigation that may still be running. The measurement waits for the app to mount, and retries once if the context goes away underneath it. A second failure is a real one.
The rule styling a result group's label declared font-size twice and the second one won, so "Transaction" rendered at the same size as the transaction it was introducing. The grouping did no work: every line in the dropdown looked like a result. It takes the eyebrow register now, which is the one place in this design system where small caps and wide tracking are allowed. The results lead, the labels sit above them, and the dropdown is shorter for it. Search recognises the identifier classes it should, keyboard selection walks the list, and the selected row takes the brand tint. Checked against a transaction id, an address, a height, a prefix, and a string that is none of those, in both themes.
…t of the system The file index named three gates and the matrix. There are seven gates and two harnesses, and four of them were added by the work that found the defects they now prevent. The note on fixtures is the more important half. Five defects on this product were invisible for as long as they existed because the surface that showed them had no fixture and rendered as a skeleton, an error, or a flat shape in every screenshot ever taken. A route that renders nothing passes every automated check, so a missing fixture does not weaken a review. It removes it, and reports success.
The gate failed its first honest run against every route, and each failure was the gate being wrong rather than the page. It probed at a fixed pause, which is a race and not a deadline: on a loaded CI machine a page that finishes perfectly well is still mid-render at 2.6 seconds. It now waits for the page to settle, up to fifteen seconds, and what fails a run is a page that never settles. It counted a spinner nobody can see. The block overview keeps its loader in the tree and fades the wrapper with opacity, so checkVisibility is used now, which walks the ancestors and understands opacity. It counted app-loading-indicator, which is a labelled progress banner and is supposed to stay for as long as the work runs. Failing a release for saying "Indexing blocks" is the opposite of the point. It counted placeholders below the fold. Angular defers work until it scrolls into view, so a block page holds a transaction placeholder there on purpose. The harness judges a viewport, so the probe does too. All thirteen routes pass now, and the judge's own tests still hold it to firing on the states that shipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Run across every route and data state, the gate found four more pages with the same fault: home and blocks hold a placeholder with nothing said about why when the chain backend is down, and the transaction and address pages render bare skeletons with no accessible announcement, so a screen reader user hears nothing while they wait. Those are real and were found here, but they are not what this change set reviewed, and quietly widening the blast radius to make a gate green is how gates end up disabled. The gate blocks on the three routes whose request lifecycle has been rebuilt, and prints the rest every run under their own heading, never suppressed. Adding a route to GATED_ROUTES is how that work gets finished. The loading rule also stopped demanding a spinner from pages that have nothing to fetch. The docs and source pages render from the bundle, and failing them for not waiting was the rule being wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three conflicts, all import adjacency where both sides added lines in the same place. Their subject is how these charts load; this branch's is how they are drawn, so nothing contended. The gitignore takes their wider artifacts-*/ pattern, which already covers the directory this branch added. The statistics component no longer carries a zoom slider after their rewrite, so it no longer needs the shared slider style; the mempool graph it delegates to still does, and all nineteen chart components that draw a slider still spread it.
…p it lying The unfinished-page gate was failing the mining dashboard at three widths with "2 skeletons never resolved". It was right: the hashrate and difficulty panels had no fixture, so they waited forever. This branch had already pinned those, and with them the gate goes from six blocking failures to none. The block page reported the same fault at 1024 and only at 1024, and there the gate was wrong. That section is behind Angular's @defer (on viewport), so it is supposed to stay a placeholder until the reader scrolls to it. The probe allows a 200px margin around the viewport while an IntersectionObserver uses none, so at that one width it counted a placeholder Angular had correctly not replaced. The capture now scrolls to the bottom and back before it judges. Deferred content loads, the view returns to the top so screenshots are unchanged, and a skeleton still showing afterwards is genuinely stuck rather than merely patient. Two more fixture gaps went with it: the neighbouring block on the chain strip was unanswered, and a block declaring three thousand transactions returned one, so the list sat waiting for the rest of a page that never came. 45 screenshots across five routes, three widths and three themes: 0 unfinished pages, 0 contrast failures, 0 accessibility violations, 0 overflow, 0 console errors, 0 blank canvases.
Author
|
Coordination note: PR 6 is being driven to merge and deployment by the session that owns fix/universe-explorer-production-go-state, which gained three commits after your ae4ba8e merge: 867717a (home, blocks, tx and address join the gate), 8c9a539 (tracker stage contrast), 6d48139 (per-run CI port with an identity check on the server). After PR 6 reaches develop, merge the new develop into this branch to pick those up; the fixed-port serve step this branch still carries will otherwise keep colliding with concurrent runs on a shared host. |
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.
What this is
Universe Explorer shipped an ultramarine identity that belonged to no other
Bitcoin Universe surface. Core, Wallet and Inscribe are all built on the same
pink-forward system, so the explorer read as a different company's product
wearing our name. This replaces the brand layer.
Why pink can be loud here
One rule makes it work at this density: pink is identity and intent, never a
fact about Bitcoin. It marks the mark, the primary action, active navigation,
selection and live surfaces. It is never a status, a protocol, a fee band or a
quantity, and CI fails the build if a brand role drifts within 25 dE of a state
or protocol colour.
So the fee scale is untouched, green still means proven, red still means
unavailable, and Bitcoin orange stays where it means Bitcoin.
Values were derived, not picked
#ff0066is the Bitcoin Universe anchor and it is 3.85:1 on white, which isexactly why it paints borders, rings, rules and display type and never a small
label. Filled controls take
#c40059, measured at 6.01:1 in both directions.The chart series were solved for separation under protanopia, deuteranopia and
tritanopia as well as normal vision.
The research behind it
A visual language pass read each competitor's declared shell colour out of its
own markup and found the category uniform: dark and cool, with a documented
convention of low chroma. Light, warm and chromatic is an empty position, and
the pass checked whether it is empty for a good reason before taking it. Two
further passes on different strategies produced no new competitor, capability
class or trend, which is the closure condition.
docs/research/visual-language-benchmark.mdholds the method, themeasurements and the sources.
What the gate found
Adding the role-separation check surfaced three collisions that predate this
change:
#D81B60, 6.7 dE from the brand pinkDefects fixed along the way
A loud colour is useful that way. It shows you where the hierarchy was already
broken.
marked the choice with
.activealone, so nothing answered "which period amI looking at". Same for the Mempool/Mining switcher and the RBF filter.
blue measuring about 1.15:1 against the page, on eighteen charts.
(undefined undefined: )for anyfailure that was not an HTTP response: the pipe read
.statusoff whatever itwas handed, and its null guard came after the property access.
stale and it could only ever pass on the Linux runner.
Checks
through the gloss layer in all three themes
dist:root, so there is no first-frame flashNote on sequencing
This branch is based on
origin/developat09ec717and was developed in aseparate worktree so it could not collide with the work in #6. It should merge
after #6 lands.
🤖 Generated with Claude Code