fix(openspec): the tab strip wraps by design, and 55 citations named an archived change - #2063
Conversation
…ot one line Row A33 asked for the six work tabs to share one line at 1024. Measured, they cannot: six tabs need 661px on a line and this strip's tab row has about 280, and even a full-width strip yields roughly 570. The wrap is not a defect either. CnTabs wraps rather than scrolls on purpose, because a scrolling strip hides tabs behind an edge with nothing to say they are there. So the requirement now asks for what the handler actually loses when this breaks: the strip above the fold, and every tab visible, unclipped and on screen. Three spec files and the design note carried the old wording; all four now say the same thing.
…rip-wraps-by-design
#2057 moved 44 changes into `openspec/changes/archive/`. It did not repoint the tests that cite them, so 27 spec paths across 17 e2e files named a file that is no longer there, in 119 places. A citation that resolves to nothing reads as verified and is not. Each one now points at the canonical spec where the anchor verifiably exists, and at the archived copy where it does not: the two `tasks.md` task ids, the kanban keyboard scenarios and the workflow-editor canvas scenarios. The five-work-tab scenario has a live successor, `the-strip-holds-six-tabs-and-no-more`, so its two citations go there rather than to the archive. Comment lines only. `playwright test --list` still resolves 381 tests in 67 files. Four citations remain broken and are NOT from #2057: bezwaar-management, case-map, subsidy-intake and workflow-board. `git log --all` finds no commit that ever added those spec files, and their anchors appear nowhere in `openspec/`, so those tests cite a spec that was never written. That needs a decision about the specs, not a repoint.
Quality Report — ConductionNL/dossiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue3-compile | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 549/549 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-09 10:03 UTC
Download the full PDF report from the workflow artifacts.
`bezwaar-management`, `case-map`, `subsidy-intake` and `workflow-board` have no spec file, and `git log --all` finds no commit that ever added one. Their anchors appear nowhere under `openspec/`. So six citations across two e2e files claimed coverage against nothing, and read as verified. Four of the six had a real home and now point at it: - the workflow board, twice, at dashboard's `DASH-V1-006a: Board columns reflect status types`, which is the kanban of statuses the test opens - the case map at `case-map-overview`'s `OVERVIEW-01a: Display all cases on map` - the bezwaar list at `bezwaar-beroep-workflow`'s `Bezwaar index shows only bezwaar cases with correct columns` Two have no home and now say so with `@e2e exclude` and a reason, the form `dutch-value-l10n.spec.ts` already uses. The subsidie specs cover the keten and the settlement of case costs, not the page that lists the aanvragen. And bezwaar-beroep-workflow specifies the transitions, not a list re-rendering after one. Neither test is removed; each pins its behaviour until a scenario exists. Stacked on #2063, which repairs the 27 paths #2057 archived. Merge that first. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
|
Admin-merging on measured evidence rather than on a green tick, because dossiq's E2E suite is red on
This PR (run 34334673324): 299 passed, 6 failed, 2 flaky. The six are development's seven minus The other six are not reachable from here either: this PR's only changes to The remaining six are being worked in the parallel stream that produced #2007, #2042 and #2045. |
… it taught #2067 fixed the same six dangling anchors while this branch was writing them, and did it better on both counts. It anchors at the SCENARIO (`scenario-dash-v1-006a-board-columns-reflect-status-types`) rather than at the requirement, which is the truer link because a test proves a scenario. And for the two anchors with no home anywhere it uses `@e2e exclude` with a reason, where this branch had pointed them at the nearest plausible capability. That second difference is the one worth keeping. An anchor that resolves while naming the wrong requirement is worse than one that does not resolve: it reads as verified coverage, it satisfies every mechanical check, and the gap stays invisible until somebody opens the cited spec and finds the scenario absent. This branch's version would have passed gate 46 and been wrong. Both test files are taken whole from `origin/development` on merge. The rule is adopted into the convention as its fourth requirement. What survives here is what neither #2063 nor #2067 carries: the measurement. 2,955 anchors read as dangling under `os.path.exists()` and none of them is, because gate 46 resolves by capability across three homes rather than by path. Zero unresolved across 1,698 files, mutation-checked before it was believed. The six real ones were `@e2e`, and gate 46's pattern is `@spec\s+(openspec/...)`, so 284 `@e2e` anchors here are checked by nothing. That is why they were found by hand twice instead of by CI once, and it is the upstream fix worth having. Also recorded: why #2063's 55 repointed citations were right work even with nothing failing. A change directory names no capability, so the archive index rescues it by change NAME, a weaker guarantee than the rule. And the process lesson, because it cost this branch: search before building. Two PRs on this exact topic landed while it was being written, and it went out CONFLICTING with 4 of 49 checks green.
…ep that was a measurement error (#2116) * fix(e2e): six dangling spec anchors, and the sweep that turned out not to exist I reported roughly 420 anchors broken by archiving, then re-measured and got 2,955. Both numbers are wrong, and wrong the same way: they check `os.path.exists()` on the literal path an anchor spells. Gate 46 does not resolve anchors by path. It builds a capability index spanning the three homes a spec has in its life: the open change's delta, the archived change's delta, and the canonical `openspec/specs/<cap>/spec.md`. Keyed on the capability, an anchor written once keeps resolving through the archive move and the promotion after it. Run over all 1,698 files in scope, the gate's own resolver reports ZERO unresolved targets. That zero was mutation-checked before it was believed. Two anchors were planted, one naming a change that does not exist and one a capability that does not, and both were reported as "target file not found"; then restored, and the diff confirmed clean. An instrument that cannot fail does not get to say zero. Six anchors ARE dangling, and widening the check rather than narrowing it is what found them. All six are `@e2e`, and gate 46 reads only `@spec`: its pattern is `@spec\s+(openspec/...)`. All 286 `@e2e` anchors in this repo are checked by nothing, which is exactly why these six rotted unnoticed. They name four capabilities with no home anywhere: `workflow-board`, `bezwaar-management`, `case-map`, `subsidy-intake`. Repointed at capabilities that exist, each fragment probed through the resolver BEFORE it was written rather than after: `dashboard#REQ-DASH-V1-006` twice, `case-map-overview#REQ-OVERVIEW-01`, `bezwaar-lifecycle#bezwaren-list-surface` and `#bezwaar-status-types`, and `subsidieverlening-keten` for the subsidies test, which is fixme'd on #719 and whose surface no requirement covers yet. That last one carries a comment saying so, because naming a heading nobody wrote would be a false anchor. The convention is written down as `spec-anchor-convention` rather than left in a PR body. An anchor names the capability in its canonical spelling and is never rewritten; a change directory names no capability, so nothing can carry it forward. The pending case is stated explicitly: when the requirement lives in a change that has not been archived, the anchor still names the canonical path even though no file stands there yet. It is early, not wrong, and it resolves through the open change's delta meanwhile. 286 anchors here are in that state and all 286 resolve. The number that settles it against a path rule: 107 anchors name `analytics-dashboard-surface`, whose only home is the open `page-topology-cleanup`. A path rule breaks all 107 the day that change lands. The capability rule makes them all correct that day, with no edit. After: 6,244 anchors, zero unresolvable, gate 46 clean. * docs(openspec): drop the anchor fix #2067 landed first, keep the rule it taught #2067 fixed the same six dangling anchors while this branch was writing them, and did it better on both counts. It anchors at the SCENARIO (`scenario-dash-v1-006a-board-columns-reflect-status-types`) rather than at the requirement, which is the truer link because a test proves a scenario. And for the two anchors with no home anywhere it uses `@e2e exclude` with a reason, where this branch had pointed them at the nearest plausible capability. That second difference is the one worth keeping. An anchor that resolves while naming the wrong requirement is worse than one that does not resolve: it reads as verified coverage, it satisfies every mechanical check, and the gap stays invisible until somebody opens the cited spec and finds the scenario absent. This branch's version would have passed gate 46 and been wrong. Both test files are taken whole from `origin/development` on merge. The rule is adopted into the convention as its fourth requirement. What survives here is what neither #2063 nor #2067 carries: the measurement. 2,955 anchors read as dangling under `os.path.exists()` and none of them is, because gate 46 resolves by capability across three homes rather than by path. Zero unresolved across 1,698 files, mutation-checked before it was believed. The six real ones were `@e2e`, and gate 46's pattern is `@spec\s+(openspec/...)`, so 284 `@e2e` anchors here are checked by nothing. That is why they were found by hand twice instead of by CI once, and it is the upstream fix worth having. Also recorded: why #2063's 55 repointed citations were right work even with nothing failing. A change directory names no capability, so the archive index rescues it by change NAME, a weaker guarantee than the rule. And the process lesson, because it cost this branch: search before building. Two PRs on this exact topic landed while it was being written, and it went out CONFLICTING with 4 of 49 checks green. --------- Co-authored-by: Ruben van der Linde <release-bot@conduction.nl>
Closes #2012.
Two things, both about specs that no longer say what the code does.
1. Row A33 asked for something that cannot happen
Row A33 said the six work tabs share one line at 1024, and
case-header.spec.tsasserted it. It has been failing on
development. I measured it rather thanguessing, rendering the six tabs with the exact
.cn-tabs__nav-itemrules fromnc-vue 2.41.0:
So one line is not reachable at 1024 with these labels, at any grid width. The
strip sits at 8 of 12 columns, and moving it to 12 does not close a 661 against
570 gap either.
The wrap is also not a defect. nc-vue's own CSS carries the argument, in
writing, on
.cn-tabs__nav:Two requirements were in conflict and one of them was wrong. The requirement now
asks for the thing the handler actually loses when the strip breaks: the strip
above the fold, and every tab visible, unclipped and on screen. The test asserts
each tab has a real box, starts at or after x=0 and ends at or before x=1024,
with a length check so a strip that rendered nothing cannot pass by having
nothing to assert on.
The alternatives, for the record: a full-width strip with the icons dropped fits
on 517 against 570 but restacks the whole right column, and an icon-only
breakpoint in
CnTabsfixes every app at once but needs a nextcloud-vue releaseto reach here and makes a tab harder to scan.
2. #2057 archived 44 changes and left 119 citations pointing at nothing
#2057 moved 44 changes into
openspec/changes/archive/. It did not repoint thetests that cite them, so 27 spec paths across 17 e2e files name a file that is
no longer there, in 119 places. A citation that resolves to nothing reads as
verified and is not.
Each one now points at the canonical spec where the anchor verifiably exists,
and at the archived copy where it does not: the two
tasks.mdtask ids, the twokanban keyboard scenarios and the two workflow-editor canvas scenarios. The
five-work-tab scenario has a live successor,
the-strip-holds-six-tabs-and-no-more,so its two citations go there rather than to the archive.
Comment lines only, and
playwright test --liststill resolves 381 tests in 67files.
What this does not fix
Four citations stay broken and they are not from #2057:
bezwaar-management,case-map,subsidy-intakeandworkflow-board.git log --allfinds nocommit that ever added those spec files, and their anchors appear nowhere under
openspec/, socomplaints-bezwaar.spec.tsandworkflow-operations.spec.tscite a spec that was never written. That needs a decision about whether to write
the spec or drop the claim, not a repoint.