Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,10 @@ jobs:
rm -f "${refused}"
states=$(
for dep in $(read_api "repos/${REPO}/deployments?sha=${SHA}&per_page=100" '.[].id'); do
read_api "repos/${REPO}/deployments/${dep}/statuses?per_page=100" '.[].state'
read_api "repos/${REPO}/deployments/${dep}/statuses?per_page=100" 'max_by([.created_at, .id]) | .state // empty'
done
read_api "repos/${REPO}/commits/${SHA}/statuses?per_page=100" \
'.[] | select(.context | ascii_downcase | test("vercel")) | .state'
'group_by(.context)[] | max_by([.created_at, .id]) | select(.context | ascii_downcase | test("vercel")) | .state'
# A refused read prints nothing and is recorded in ${refused}, read just below, so the
# subshell's own exit status is not the signal and must not trip `set -e`.
true
Expand Down Expand Up @@ -654,9 +654,10 @@ jobs:
- name: Public surface -- production contracts
if: ${{ always() && steps.preview.outputs.ready == 'true' }}
env:
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report/public-contracts
PW_BASE_URL: https://frequencylocal.com
PW_PROBE_CRON: '0'
run: pnpm exec playwright test public-surface.spec.ts --project mobile
run: pnpm exec playwright test public-surface.spec.ts --project mobile --output=test-results/public-contracts

# ── THE VISUAL COMPARE, IN TWO TIERS (2026-09-14) ──────────────────────────────────
#
Expand Down Expand Up @@ -701,25 +702,27 @@ jobs:
if: ${{ always() && steps.preview.outputs.ready == 'true' }}
env:
PW_BASE_URL: ${{ steps.preview.outputs.url }}
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report/visual-public
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
PW_STORAGE_STATE: ${{ steps.session.outputs.path }}
PW_ROOM_PATH: ${{ vars.PW_ROOM_PATH }}
PW_SPACE_SLUG: ${{ vars.PW_SPACE_SLUG }}
PW_REQUIRE_SHELL: ${{ vars.PW_REQUIRE_SHELL || secrets.PW_REQUIRE_SHELL }}
PW_REQUIRE_OPERATOR: ${{ vars.PW_REQUIRE_OPERATOR || secrets.PW_REQUIRE_OPERATOR }}
run: pnpm test:e2e:visual:stable
run: pnpm test:e2e:visual:stable --output=test-results/visual-public

- name: Visual compare -- member shell + operator
if: ${{ always() && steps.preview.outputs.ready == 'true' }}
env:
PW_BASE_URL: ${{ steps.preview.outputs.url }}
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report/visual-shell
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
PW_STORAGE_STATE: ${{ steps.session.outputs.path }}
PW_ROOM_PATH: ${{ vars.PW_ROOM_PATH }}
PW_SPACE_SLUG: ${{ vars.PW_SPACE_SLUG }}
PW_REQUIRE_SHELL: ${{ vars.PW_REQUIRE_SHELL || secrets.PW_REQUIRE_SHELL }}
PW_REQUIRE_OPERATOR: ${{ vars.PW_REQUIRE_OPERATOR || secrets.PW_REQUIRE_OPERATOR }}
run: pnpm test:e2e:visual:shell
run: pnpm test:e2e:visual:shell --output=test-results/visual-shell

# LIVE-313 / ADR-1449: the member-shell step above is a real gate. /discover stays in
# its own advisory step (LIVE-373) because its HEIGHT is production Circles, events and
Expand All @@ -741,13 +744,14 @@ jobs:
if: ${{ always() && steps.preview.outputs.ready == 'true' }}
env:
PW_BASE_URL: ${{ steps.preview.outputs.url }}
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report/visual-advisory
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
PW_STORAGE_STATE: ${{ steps.session.outputs.path }}
PW_ROOM_PATH: ${{ vars.PW_ROOM_PATH }}
PW_SPACE_SLUG: ${{ vars.PW_SPACE_SLUG }}
PW_REQUIRE_SHELL: ${{ vars.PW_REQUIRE_SHELL || secrets.PW_REQUIRE_SHELL }}
PW_REQUIRE_OPERATOR: ${{ vars.PW_REQUIRE_OPERATOR || secrets.PW_REQUIRE_OPERATOR }}
run: pnpm test:e2e:visual:advisory
run: pnpm test:e2e:visual:advisory --output=test-results/visual-advisory

# An advisory tier that fails silently is the thing it was built to avoid, so say it in
# the summary where a reviewer already looks. EVERY surface on the tier is named here, with
Expand Down Expand Up @@ -1106,10 +1110,10 @@ jobs:
rm -f "${refused}"
states=$(
for dep in $(read_api "repos/${REPO}/deployments?sha=${SHA}&per_page=100" '.[].id'); do
read_api "repos/${REPO}/deployments/${dep}/statuses?per_page=100" '.[].state'
read_api "repos/${REPO}/deployments/${dep}/statuses?per_page=100" 'max_by([.created_at, .id]) | .state // empty'
done
read_api "repos/${REPO}/commits/${SHA}/statuses?per_page=100" \
'.[] | select(.context | ascii_downcase | test("vercel")) | .state'
'group_by(.context)[] | max_by([.created_at, .id]) | select(.context | ascii_downcase | test("vercel")) | .state'
# A refused read prints nothing and is recorded in ${refused}, read just below, so the
# subshell's own exit status is not the signal and must not trip `set -e`.
true
Expand Down
5 changes: 5 additions & 0 deletions app/api/status/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ describe('the build identity a deploy check reads', () => {
})

describe('the index contract it already had is unchanged', () => {
it('publishes the one-hour browser and shared-cache policy at the origin', () => {
const directives = GET().headers.get('cache-control')?.split(',').map((directive) => directive.trim())
expect(directives).toEqual(['public', 'max-age=3600', 's-maxage=3600'])
})

it('still lists the endpoint family and the SLO summary', async () => {
const json = await body()
expect(json.service).toBe('frequency')
Expand Down
13 changes: 10 additions & 3 deletions components/frequency/site/pages/home/weave.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,25 @@ export function Weave({ stages }: { stages: WeaveStage[] }) {
const s = svg.current
if (!host || !s) return
const R = host.getBoundingClientRect()
// Measure each card once, and keep path writes detached until every read is complete.
const boxes = new Map<Element, { x: number; y: number; w: number; h: number; cx: number; cy: number }>()
const box = (el: Element) => {
const cached = boxes.get(el)
if (cached) return cached
const r = el.getBoundingClientRect()
return { x: r.left - R.left, y: r.top - R.top, w: r.width, h: r.height, cx: r.left - R.left + r.width / 2, cy: r.top - R.top + r.height / 2 }
const result = { x: r.left - R.left, y: r.top - R.top, w: r.width, h: r.height, cx: r.left - R.left + r.width / 2, cy: r.top - R.top + r.height / 2 }
boxes.set(el, result)
return result
}
s.replaceChildren()
const paths = document.createDocumentFragment()
const add = (d: string, colour: string, cols: number[], lane: Row, cls: string[]) => {
const p = document.createElementNS(SVG_NS, 'path')
p.setAttribute('d', d)
p.setAttribute('stroke', colour)
cls.forEach((c) => p.classList.add(c))
p.dataset.cols = cols.join(',')
p.dataset.lane = lane
s.appendChild(p)
paths.appendChild(p)
}
const link = (a: Element | null, b: Element | null, colour: string, cols: number[], flow: boolean, lane: Row) => {
if (!a || !b) return
Expand Down Expand Up @@ -123,6 +129,7 @@ export function Weave({ stages }: { stages: WeaveStage[] }) {
setStageBoxes([])
setLoopTops(null)
}
s.replaceChildren(paths)
}, [mobile, stages])

// Draw after layout, on resize (debounced), and once the web fonts settle the boxes.
Expand Down
19 changes: 19 additions & 0 deletions docs/ledger/rows/CI-VISUAL-ARTIFACT-RETENTION.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"id": "CI-VISUAL-ARTIFACT-RETENTION",
"title": "Preserve every visual tier failure artifact through the final CI upload",
"status": "open",
"priority": "P2",
"lane": "hygiene",
"size": "S",
"source": {
"file": ".github/workflows/e2e.yml",
"ref": "Visual compare public/member/advisory steps and final Upload report + diffs"
},
"detail": "Read-only audit of exact control run41904712802 found112 blocking public/member visual failures in logs, but its final official artifact13361658194 contains only6 final advisory Discover cases:4failed and2skipped. Independent Playwright invocations reuse default test-results and playwright-report, so later tiers erase earlier screenshots/reports before the single final upload. Isolate both output and HTML report directories per tier without changing selectors, thresholds, baselines, timeouts, secrets or blocking/advisory semantics. Preserve initial smoke/root evidence too. Related broader audit SCAN-1032 remains open. This repair preserves evidence only; it does not resolve visual differences, demonstrate a visual/performance pass or authorize baseline recapture.",
"verify": {
"kind": "manual",
"evidence": "OPEN. Daniel approved exact workflow patch SHA256 05b29b302606bea428c260a5e22b617a28c4dbd95767ee04d72eaaea9aa0c7f4 on 2026-11-04. Applied against main bbdfba1b: exactly six YAML leaves isolate public/member/advisory results and HTML directories; upload roots, selectors, secrets, gates, thresholds, baselines and blocking/advisory semantics preserved. Installed pnpm/Playwright non-browser controls reproduce pristine advisory-only retention and preserve candidate smoke plus all three visual tiers; intentional public/advisory exit1 and reporter append controls pass. Exact-head CI and inspection of actual retained artifacts remain pending. Local fixtures do not establish a visual or performance pass; do not close until CI artifact retention is verified.",
"checked": "2026-11-04"
},
"wave": "W7"
}
10 changes: 9 additions & 1 deletion test/e2e/public-surface.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,19 @@ test.describe('public surface (platform host)', { tag: '@public-surface' }, () =
const res = await request.get('/api/status')
expect(res.status()).toBe(200)
expect(res.headers()['content-type']).toContain('application/json')
expect(res.headers()['cache-control']).toMatch(/s-maxage=\d+/)
// Vercel consumes s-maxage at its edge. Check the client policy and actual cache reuse.
const cacheControl = res.headers()['cache-control'].split(',').map((directive) => directive.trim())
expect(cacheControl).toEqual(expect.arrayContaining(['public', 'max-age=3600']))
expect(cacheControl).not.toContain('private')
expect(cacheControl).not.toContain('no-store')
expect(res.headers()['set-cookie'], 'a cacheable machine endpoint sets no cookie').toBeUndefined()
const json = await res.json()
expect(json.service).toBe('frequency')
expect(json.build.env).toBe('production')
await expect.poll(async () => {
const cached = await request.get('/api/status')
return cached.ok() ? cached.headers()['x-vercel-cache'] : cached.status()
}, { message: '/api/status is served from the Vercel cache after warming' }).toBe('HIT')
})

test('unknown API paths and anonymous private APIs never 5xx and answer JSON-or-empty', async ({ request }) => {
Expand Down
Loading