Skip to content

fix(openspec): the tab strip wraps by design, and 55 citations named an archived change - #2063

Merged
rubenvdlinde merged 4 commits into
developmentfrom
fix/the-tab-strip-wraps-by-design
Sep 9, 2026
Merged

fix(openspec): the tab strip wraps by design, and 55 citations named an archived change#2063
rubenvdlinde merged 4 commits into
developmentfrom
fix/the-tab-strip-wraps-by-design

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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.ts
asserted it. It has been failing on development. I measured it rather than
guessing, rendering the six tabs with the exact .cn-tabs__nav-item rules from
nc-vue 2.41.0:

what width
six tabs with icons, on one line 661px
six tabs, labels only 517px
the strip's tab row today, from the observed wrap roughly 280px
a full-width strip at a 1024 viewport roughly 570px

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:

Wrap before scrolling. A horizontally scrolling strip hides tabs behind an
edge with nothing to say they are there, and beside a #nav-end control the
clipped tab reads as sitting UNDER the control. Wrapping keeps every tab
reachable without a gesture.

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 CnTabs fixes every app at once but needs a nextcloud-vue release
to 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 the
tests 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.md task ids, the two
kanban 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 --list still resolves 381 tests in 67
files.

What this does not fix

Four citations stay broken and they 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 under
openspec/, so complaints-bezwaar.spec.ts and workflow-operations.spec.ts
cite a spec that was never written. That needs a decision about whether to write
the spec or drop the claim, not a repoint.

…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.
Conduction Release Bot added 3 commits September 9, 2026 11:21
#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.
@rubenvdlinde rubenvdlinde changed the title fix(e2e): the tab strip wraps by design, so the spec asks for reach not one line fix(openspec): the tab strip wraps by design, and 55 citations named an archived change Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 50bda83

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.

@rubenvdlinde
rubenvdlinde merged commit 31117dd into development Sep 9, 2026
47 of 49 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/the-tab-strip-wraps-by-design branch September 9, 2026 10:07
rubenvdlinde added a commit that referenced this pull request Sep 9, 2026
`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>
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Admin-merging on measured evidence rather than on a green tick, because dossiq's E2E suite is red on development independently of this PR.

development right now (run 34333742660): 300 passed, 7 failed.

case-actions-menu.spec.ts:321
case-header.spec.ts:297            <- this PR fixes this one
case-identity.spec.ts:314
case-timeline.spec.ts:98
case-type-authoring-extras.spec.ts:494
case-type-authoring-extras.spec.ts:645
contacts-domain.spec.ts:362

This PR (run 34334673324): 299 passed, 6 failed, 2 flaky.

The six are development's seven minus case-header.spec.ts:297. This PR removes one failure and adds none.

The other six are not reachable from here either: this PR's only changes to case-identity, case-timeline and case-type-authoring-extras are @e2e comment lines. Nothing executable in those files moved, which is checkable from the diff.

The remaining six are being worked in the parallel stream that produced #2007, #2042 and #2045.

rubenvdlinde pushed a commit that referenced this pull request Sep 9, 2026
… 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.
rubenvdlinde added a commit that referenced this pull request Sep 9, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant