merge(13): bring main forward — unblocks the crate's test build
The lane merged main at0c4826f8, one commit before0de67ca6added auth/auth_rationale to PortMapping's test constructors in prod_orchestrator.rs. That left the lane unable to compile ANY test in the archipelago crate, which is why 13-05 could not observe its 13 tests pass (window 19). Not a defect in this phase's work — just staleness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> # Conflicts: # core/archipelago/src/main.rs
This commit is contained in:
+15
-1
@@ -324,8 +324,22 @@ html.controller-nav [data-controller-container]:focus {
|
||||
|
||||
/* Dashboard content lives inside animated perspective/scroll containers.
|
||||
Chromium/Brave can corrupt backdrop-filter + transformed cards into black
|
||||
square/rectangle layers, so use translucent fills there instead. */
|
||||
square/rectangle layers, so use translucent fills there instead.
|
||||
|
||||
`.home-card-shell` (Home.vue) was missing from this list and kept its own
|
||||
`backdrop-filter: blur(18px)`, producing a second, subtler form of the
|
||||
same corruption: a vertical seam where the blurred backdrop is refreshed
|
||||
on one side and stale on the other. Because the boundary is in SCREEN
|
||||
space, it cut both dashboard cards at the same x and vanished in the gap
|
||||
between them — which is how it was identified from a screenshot
|
||||
(2026-08-03: a lone unpaired brightness step at CSS x=633, present on
|
||||
10/13 sampled rows inside the cards and 2/10 in the gap). It surfaced on
|
||||
hover because a hover repaint is what re-rasterises part of the
|
||||
backdrop. The card's fill is already rgba(0,0,0,0.65) — the same as
|
||||
.glass-card, which renders unblurred here — so dropping the blur also
|
||||
makes the shell consistent with the tiles beside it. */
|
||||
body.dashboard-active .dashboard-scroll-panel .glass-card,
|
||||
body.dashboard-active .dashboard-scroll-panel .home-card-shell,
|
||||
body.dashboard-active .dashboard-scroll-panel .glass,
|
||||
body.dashboard-active .dashboard-scroll-panel .mode-switcher,
|
||||
body.dashboard-active .dashboard-scroll-panel .glass-button,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
/**
|
||||
* Chromium/Brave mis-rasterise `backdrop-filter` inside the dashboard's
|
||||
* animated perspective/scroll containers. style.css already neutralises it
|
||||
* for the shared glass classes, but that list is hand-maintained: a component
|
||||
* that declares its own `backdrop-filter` in a local <style> block is simply
|
||||
* not covered, and nothing fails.
|
||||
*
|
||||
* That is exactly how the 2026-08-03 seam shipped. `.home-card-shell` carried
|
||||
* `backdrop-filter: blur(18px)` in Home.vue and was missing from the list, so
|
||||
* a hover repaint left a vertical line where the refreshed backdrop met the
|
||||
* stale one — visible in both dashboard cards at the same screen x, and
|
||||
* absent in the gap between them.
|
||||
*
|
||||
* This test makes the omission fail loudly instead of shipping as a glitch
|
||||
* nobody can reproduce on demand.
|
||||
*/
|
||||
|
||||
const root = resolve(__dirname, '../../..')
|
||||
const styleCss = readFileSync(resolve(root, 'src/style.css'), 'utf8')
|
||||
|
||||
/** The selector list that disables backdrop-filter on the dashboard. */
|
||||
function dashboardMitigationBlock(): string {
|
||||
const start = styleCss.indexOf('body.dashboard-active .dashboard-scroll-panel .glass-card')
|
||||
expect(start, 'dashboard backdrop-filter mitigation block not found').toBeGreaterThan(-1)
|
||||
const end = styleCss.indexOf('}', start)
|
||||
return styleCss.slice(start, end)
|
||||
}
|
||||
|
||||
/** Class selectors that declare a non-none backdrop-filter in a .vue file. */
|
||||
function blurredClassesIn(relPath: string): string[] {
|
||||
const src = readFileSync(resolve(root, relPath), 'utf8')
|
||||
const found = new Set<string>()
|
||||
// Match `.some-class { ... backdrop-filter: <not none> ... }` on one line,
|
||||
// which is how these single-line rules are written in this codebase.
|
||||
const ruleRe = /(\.[a-zA-Z0-9_-]+)\s*\{([^}]*)\}/g
|
||||
let m: RegExpExecArray | null
|
||||
while ((m = ruleRe.exec(src)) !== null) {
|
||||
const selector = m[1]
|
||||
const body = m[2]
|
||||
if (!selector || !body) continue
|
||||
const decl = /(?:^|[;{\s])backdrop-filter\s*:\s*([^;]+)/.exec(body)
|
||||
if (decl?.[1] && decl[1].trim() !== 'none') found.add(selector)
|
||||
}
|
||||
return [...found]
|
||||
}
|
||||
|
||||
describe('dashboard backdrop-filter mitigation', () => {
|
||||
it('covers every backdrop-filter surface Home.vue defines itself', () => {
|
||||
const block = dashboardMitigationBlock()
|
||||
const uncovered = blurredClassesIn('src/views/Home.vue').filter(
|
||||
(sel) => !block.includes(`.dashboard-scroll-panel ${sel},`),
|
||||
)
|
||||
expect(
|
||||
uncovered,
|
||||
`these Home.vue classes declare backdrop-filter but are not in the ` +
|
||||
`body.dashboard-active .dashboard-scroll-panel mitigation list in style.css, ` +
|
||||
`so Chromium will leave repaint seams across the dashboard cards`,
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('still lists the shared glass classes', () => {
|
||||
// Guards against someone "cleaning up" the list and silently reopening
|
||||
// the original black-rectangle corruption this block was written for.
|
||||
const block = dashboardMitigationBlock()
|
||||
for (const sel of ['.glass-card', '.glass-button', '.home-card-shell']) {
|
||||
expect(block).toContain(`.dashboard-scroll-panel ${sel},`)
|
||||
}
|
||||
})
|
||||
|
||||
it('the mitigation actually disables the filter', () => {
|
||||
const start = styleCss.indexOf('body.dashboard-active .dashboard-scroll-panel .glass-card')
|
||||
const body = styleCss.slice(styleCss.indexOf('{', start), styleCss.indexOf('}', start))
|
||||
expect(body).toContain('backdrop-filter: none')
|
||||
expect(body).toContain('-webkit-backdrop-filter: none')
|
||||
})
|
||||
})
|
||||
@@ -362,6 +362,23 @@ init()
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
|
||||
<!-- v1.7.121-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.121-alpha</span>
|
||||
<span class="text-xs text-white/40">August 4, 2026</span>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||
<p>**Making another node "Trusted" now asks for your node password.** Trust was being handed out by machines rather than by you: any node able to reach yours could join and mark itself Trusted, because the check proved only that the caller owned the key it had just presented — never that you had approved it. Trust also spread on its own, since every peer a Trusted node advertised was added as Trusted too, so one grant quietly propagated across the whole federation. Uninvited joins are now capped at Observer, advertised peers arrive as Observers, and raising anyone to Trusted — whether by generating an invite or by changing the dropdown on a node — requires your password. Lowering trust deliberately does not, because the safe action must never be the inconvenient one. Existing peers are left exactly as they are rather than silently demoted, and each one now records how its trust was granted so you can review them.</p>
|
||||
<p>**Nodes you have peered with can be messaged straight away.** Peering was not enough: you also had to be within LoRa radio range of the other node once before chat would work. The node picked how to send a message based on which radio was plugged in, and only one of those paths knew how to reach a peer over the mesh's internet transports — so on a node with a different radio, or no radio at all, messaging a peer you had just federated with simply failed until a radio contact happened to appear. Peered nodes are reachable without radio by definition, so that choice no longer depends on the hardware. Radio is still preferred when the other node is actually in range and the message fits.</p>
|
||||
<p>The dashboard no longer flickers a vertical line across its cards. A rendering seam appeared at random while moving the mouse, because the two large cards used a background-blur effect that this system already disables everywhere else on the dashboard — that browser mis-draws it inside the dashboard's animated container, and these two cards had been missed when the workaround was written. Diagnosed from a single screenshot rather than by trying to reproduce it.</p>
|
||||
<p>The Lightning screen will actually update from now on. Its image was set to "latest", and the container system will not re-fetch a label it already holds, so nodes kept the same Lightning screen forever no matter how many updates shipped. A separate copy of the same setting used only by brand-new installs also described the screen incorrectly, so fresh installs got a screen that never answered.</p>
|
||||
<p>Apps that provide their own screens stop rebuilding themselves in a loop. On this system's own node one of them rebuilt every thirty-five seconds indefinitely, burning processor time and restarting the app each round. The node decided a rebuild was needed by comparing file dates against the image's creation date, but a rebuild that changes nothing reuses the existing image and leaves that date untouched — so the condition that triggered the rebuild was still true afterwards, forever. Nodes taking this update repair themselves the first time they check.</p>
|
||||
<p>Groundwork you can see but that does not change access yet: the node can now tell you which of its app ports answer without a login, and every port that is deliberately open — Bitcoin's peer connections for syncing the chain, Lightning's wallet connections, the Electrum wallet protocol — now has to state in writing why it is safe, so the list of exceptions is something you can read rather than something you have to discover. The login gate that will sit in front of the rest is built and proven working end to end on a real node, but it is not yet closing any ports; that arrives with the signed app catalog that tells each app to hand its address over.</p>
|
||||
<p>Releases can no longer ship an unsigned update file. Signing was skippable, and when it was skipped the release was still committed and tagged — producing an update that every node correctly refuses to install. It had been caught by hand every cycle; now the release simply stops.</p>
|
||||
<p>Known gaps, disclosed rather than buried: the 5x real-node lifecycle gate was not run for this release. App ports other than the deliberate exceptions above are still reachable without a login — the gate reports them, and closing them needs the next signed catalog. Three voice-assistant ports are open without authentication and should not be; the correct fix puts them on a private network with the assistant instead, which needs testing on a node that runs both. Two nodes on the fleet still share SSH host keys (detection shipped, rotation remains a deliberate operator decision).</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.120-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
|
||||
Reference in New Issue
Block a user