Skip to content

Release: merge development into beta - #1651

Open
github-actions[bot] wants to merge 47 commits into
betafrom
development
Open

Release: merge development into beta#1651
github-actions[bot] wants to merge 47 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

github-actions Bot and others added 3 commits August 31, 2026 13:35
The 0.4.15-beta.20260831133345 release bumped the version on beta. Without this,
development stays behind beta and the next development -> beta promotion
conflicts on the version file.

Version files resolve to development's side, which is the higher line,
so this never moves a version backwards.
….4.15-beta.20260831133345

chore(release): sync beta back into development
…eds (#1650)

The cascade spec went flaky the moment it ran on a busy machine: it hit
the default 30s test timeout, passed on retry, and reported as flaky
rather than failing. No assertion was wrong.

Customer Support is a heavy page — a queue filter, two object lists, the
interaction form, a knowledge-base search and a client-cases list all
load before the header action is usable — and driving two debounced
pickers on top does not reliably fit 30s.

The first attempt at this widened only the test that had failed. A
`--repeat-each=3 --retries=0` run then failed the OTHER test in the
file: same ceiling, different victim. The allowance is a `beforeEach`
covering the whole file for that reason.

Verified the way the first fix was not: 3 repeats, retries disabled,
6/6 passed.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ dc67cc2

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 14:12 UTC

Download the full PDF report from the workflow artifacts.

These were real ERRORS, hidden behind eslint-suppressions.json rather
than fixed. Each category is resolved on its merits, not by widening the
rules until the tree goes quiet.

102 unused catch bindings become optional catch binding — `catch {`
says "this failure is deliberately ignored" where `catch (e)` named a
variable nobody read.

50 console calls stay. Every one is `console.error` (47) or
`console.warn` (3) inside a catch that ALSO surfaces the failure to the
user: the log is the detail a support engineer needs beside the message
the user sees. `console.log` remains forbidden, and the tree contains
none.

37 `...` become `…`, and this is why it was not a find-and-replace: the
literal IS the translation key, so every one had to be migrated in
en/nl/de/es/fr/it/en_US or the string would have silently fallen back to
English. Two then tripped the companion non-breaking-space rule and had
to be migrated a second time. The l10n gate caught both rounds.

3 eqeqeq were all `x == null`, the idiomatic "null OR undefined" test —
rewriting them to `===` would have NARROWED the check and let undefined
through. The rule now carries the standard `null: 'ignore'`; everything
else still requires strict equality.

The remaining 31 are all `vue/custom-event-name-casing`, left
deliberately. This codebase has 18 multi-word events, every one
kebab-case, ZERO camelCase. Renaming them plus every listener to satisfy
the rule's default would risk orphaning handlers silently — a missed
listener does not error, the event just stops arriving. Configuring the
rule instead needs eslint-plugin-vue as a direct dependency; it is
bundled inside the Nextcloud preset, and adding a dependency to restate
a style preference is not worth it.

`npm run lint` now exits 0 with zero problems.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 7b874b2

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 14:48 UTC

Download the full PDF report from the workflow artifacts.

…more forms (#1656)

LeadForm and RequestForm carried BYTE-IDENTICAL copies of the cascade —
same eight methods, same computed, same four data keys. That is the
shape that fails quietly: a fix applied to one and not the other leaves
the other broken with every test on the first one still green.

Extracted to linkedPartyCascadeMixin. Both forms lose 121 lines each and
behave identically; the existing lead and request cascade specs pass 5/5
against the running instance after the refactor, which is the check that
matters here — the unit suite never touched these methods.

The mixin deliberately does NOT register the two dialogs, though it
easily could. Components reached through a mixin are invisible to
`vue/no-undef-components`, so the linter stops being able to tell a real
missing registration from that one, and a reader of the template has
nothing to follow. Eight lines of duplication buys back static checking.

ContactmomentQuickLog now gets the cascade. Logging a contactmoment on a
client could not record WHO you spoke to — the ticket schema has carried
a `contact` property the whole time and the form never offered it. Its
client picker also read a preloaded `_limit: 100` collection, so client
101 was unselectable with nothing in the UI to say the list had been cut
off; CnResourceSelect searches server-side instead.

PosTransactionForm gets the searchable picker but NOT the cascade. Same
preload defect, worse — 500 clients fetched on every till open to fill an
OPTIONAL picker. It gets no contact half because `posTransaction` has no
`contact` property, and no inline create because the till already has a
dedicated customer-lookup flow for the walk-in case.

Verified: lint 0, prettier clean, 55 unit tests, build 0 errors, and on
the live instance lead 3/3, request 2/2, contactmoment 2/2, POS 3/3.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 9e846a6

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 14:56 UTC

Download the full PDF report from the workflow artifacts.

These are the methods the suppression sweep (#1654) touched. Gate-16 is
diff-scoped, so touching a line inside a method makes that method
accountable for its traceability, and 62 had none.

34 carry a real citation. Every target was verified to exist before it
was written: the file resolves and the anchor matches a heading in it.
That check is not something the gate does — gate-16 matches the literal
string `@spec openspec/` and never opens the file, so a citation pointing
at a deleted spec or an invented anchor passes it silently. Several tags
already in this codebase point at `openspec/changes/…` entries that have
since been archived, which is exactly how that goes wrong, so these cite
`openspec/specs/…` — the durable home — rather than a change.

28 carry `@spec exclude` with a reason, and the reasons are not
interchangeable. Eleven are genuine plumbing: display formatters that
turn cents into euros or an ISO string into a date, a deep-clone helper,
a URL-validation predicate. The other seventeen are REAL SPEC GAPS and
say so, because the useful thing to record is which one:

  - forecast (4 methods): pipeline-insights explicitly defers its admin
    config scenario to admin-settings, and admin-settings never grew a
    forecast requirement. The handoff was written down and never landed.
  - POS staff, PIN login and roles (3), the customer-lookup modal (1),
    tender types (1), loyalty reporting (1), payment-provider discovery
    (1): the changes that specified these were archived and no spec
    inherited them. `tender` and `loyalty` appear in no current spec at
    all.
  - the cash-shift surface (2): pos-lifecycle-guard-adoption names
    posTransaction and posRefund transitions only.
  - portal session handling and branding (4): customer-portal specifies
    only the origin allow-list, portal-contribution only audience
    contribution.

Stamping all 62 with `@spec exclude plumbing` would have turned the gate
green in one pass and thrown away every one of those findings. A gate
finding is a question, and the answer to seventeen of them is "no
requirement owns this", which is worth writing down.

Verified: gate-16 count 0, lint 0, format clean, 55 unit tests, build 0
errors, l10n OK.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 1c45138

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 15:05 UTC

Download the full PDF report from the workflow artifacts.

…arnings (#1660)

Every one was `csstools/use-logical`: a physical property where the
logical equivalent belongs. 66 `text-align: left|right` become
`start|end`, 21 `margin-left` become `margin-inline-start`, and so on
through padding, borders and insets.

In a left-to-right document these render identically by definition, so
this is not a visual change. It is what makes the app translatable into
a right-to-left language without every margin, indent and accent landing
on the wrong side.

Five `left: 0; right: 0` pairs collapse into `inset-inline: 0`, which is
why the diff removes five more lines than it adds. Both values are 0, so
the start/end argument order that would otherwise matter does not.

The hazard in a change like this is not the rename, it is a HALF rename:
physical and logical longhands are different properties, so a later
`margin-left: 0` no longer cancels an earlier `margin-inline-start`, and
the override silently stops working. Every changed file was re-grepped
for a surviving `margin|padding|border-left|right`, a bare `left:`/
`right:`, or a `text-align: left|right`. None remain.

The four `border-left` conversions are all on cards and timeline items
(`.work-card--overdue`, `.pipeline-card--overdue`, `.activity-timeline__item`,
`.rule-node`) — the cases this app's CLAUDE.md explicitly permits a left
border, not table rows, which must keep using an inset box-shadow so the
accent does not shift the row's cell content.

Verified: stylelint 0, lint 0, format clean, build 0 errors.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 6a22ae2

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 15:16 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 8d163b3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-08-31 15:41 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits August 31, 2026 18:11
Dependabot cannot update these packages. Its npm updater fails with:

  npm error code EOVERRIDE
  npm error Override for <pkg> conflicts with direct dependency

The package is listed BOTH as a direct dependency and as a literal overrides
entry pinned to the same range. Dependabot bumps the direct dependency and
does not move the override, npm rejects the mismatched pair, and the update
is abandoned -- so these packages can never be updated, security advisories
included.

npm's self-reference is the idiom for exactly this: $<name> tells the
override to follow whatever the direct dependency resolves to, instead of a
range that has to be kept in lockstep by hand. This repo already uses it (see
the vue entry under @vue/test-utils).

Transitive consumers still get pinned to one version -- the direct
dependency's -- which is what the override was there to do. What changes is
that the pin tracks the dependency instead of duplicating it.

Verified in a clean container: bumping the direct dependency with the literal
override reproduces the EOVERRIDE above; with the self-reference the same
bump resolves.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…ble (#1663)

Two halves. The annotations were the ask; the checker is what the work
turned up on the way.

ANNOTATIONS
-----------
phpcs warned that 403 public methods and 144 classes across 206 files
carried no @SPEC. It exits 0 on warnings, so this never blocked anything
and never got fixed. 320 tags now carry a real citation (27 distinct
targets, every file and anchor verified); the rest carry `@spec exclude`
with a reason.

81 of the 206 files are excluded, and the reasons are not
interchangeable. The largest is not plumbing at all: THE ENTIRE CUSTOMER
PORTAL BACKEND HAS ONE REQUIREMENT. customer-portal/spec.md is 53 lines
specifying only the widget-mode origin allow-list, while auth, MFA,
sessions, tokens, delegation, documents, invoices, orders, exports and
audit -- 24 services, 12 controllers, a job and a command -- are
unspecified. Forecast and loyalty repeat the gap the frontend pass found
in #1658. Genuine plumbing (exception types, value objects,
infrastructure utilities) is excluded as such and says so.

THE CHECKER
-----------
Claiming 1,989 existing tags pointed at deleted files was wrong, twice.

Archiving a change MOVES it and adds a date prefix -- `changes/X/` becomes
`changes/archive/<date>-X/` -- so comparing literal paths reports 97
intact specs as deleted. The 98th is not archived at all: it lives in an
active change, cited at the address it will have once promoted. Nothing
was missing. A rewrite would have been pure churn, and the archive paths
it produced would break at the next reorganisation.

scripts/check-spec-links.js resolves a target against active AND archived
locations, so linking to an archived spec is as valid as linking to a
live one. It went from 887 apparent anchor failures to 0, and every step
was a dialect this repo really uses: task ids as checklist items
(`- [x] task-30:`) and section headings (`## 3.`), `[~]` as a checkbox
state, REQ ids mid-heading and in `REQ-001` form, headings cited by their
leading words, and three archived copies of one change where the anchor
lives in the second.

It caught a real error of MINE: the lead-management anchor omitted the
`[MVP]` suffix, wrong in 23 places already merged in #1656, because my
throwaway verifier slugified differently than GitHub does. That is the
argument for the checker living in the repo.

Nothing else checks this. Hydra gate-16 matches the literal string
`@spec openspec/` and never opens the file; the phpcs sniff only checks
presence. Verified it can fail: a bogus file reports UNRESOLVED, a bogus
anchor reports NO ANCHOR, both non-zero.

Verified: phpcs 547 warnings to 0, composer check:strict ALL CHECKS
PASSED (2250 tests, 8391 assertions), lint 0, format clean, 55 unit
tests, manifest OK, spec-links all resolve.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 9d14033

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 16:31 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ e67ca73

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-08-31 17:03 UTC

Download the full PDF report from the workflow artifacts.

…rs (#1666)

The eslint suppression baseline held 31 instances of one rule,
vue/custom-event-name-casing, across 14 components. Clearing it was
supposed to be a lint chore. It was not: nine of those events were
emitted in kebab-case while their parent listened in camelCase, and
Vue 2 matches event names literally, so those handlers never fired.

Dead until now:

  ProjectWbsTree   addPhase/addTask/addActivity -> ProjectDetail
                   the WBS tree's three add buttons did nothing
  ProspectCard     createLead -> ProspectWidget.onCreateLead
                   creating a lead from a prospect card did nothing
  SegmentRuleNode  validateLeaf -> SegmentBuilder.validateLeaf
                   leaf validation never ran, including recursively
  AddContactRole   searchContacts -> LeadContactRoles.searchContacts
                   contact search in the role dialog did nothing
  MissingConsent   requestConsent/skipAndSend -> BlastForm
                   both buttons on the consent modal did nothing
  WinLossWidget    rangeChange -> LeadAnalyticsSection.onRangeChange
                   the win/loss date range was ignored

Renaming the emit side to camelCase makes each listener match, so the
handlers connect for the first time. The remaining nine events had no
listener at all and are renamed for consistency.

Verified: eslint exits 0 with the suppressions file DELETED rather than
emptied, 55 unit tests pass, webpack compiles. No listener needed
changing -- they were already camelCase, which is why the mismatch was
invisible: both halves looked right on their own.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 79bc1d6

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-08-31 19:00 UTC

Download the full PDF report from the workflow artifacts.

…named (#1668)

development went red on the push after #1666. Two separate failures, both
introduced by changes that were green as PRs, because a PR into development
does not run the same set.

1. Frontend Check (format): prettier flagged src/components/LeadProducts.vue.
   Reformatted with the repo own config; `npm run format` now reports "All
   matched files use Prettier code style!" across the whole tree.

2. gate-16 spec-coverage: 5 changed methods missing @SPEC. Reproduced
   locally by scoping the gate to the offending commit
   (HYDRA_GATE_BASE_REF=HEAD~1), which named them exactly; the runner keeps
   its detail log in /tmp, so the run itself only gives the count.

   SegmentBuilder::runValidate      -> marketing-ui#scenario-visual-rule-tree-with-live-validation
   SegmentRuleNode::onFieldChange   -> marketing-ui#requirement-segment-builder-ui-composes-rule-trees
   SegmentRuleNode::onOperatorChange-> same requirement
   ReportExportPanel::onExportConfirm -> dashboard/spec.md, matching the four
                                        tags already in that file
   PinLoginModal::submit            -> @SPEC exclude

   The exclude is not laziness. PinLoginModal already carries
   `@spec exclude ... the pos-staff-pin-permissions change is archived and no
   spec inherited POS staff, PIN login or roles` on loadStaff, and that change
   directory is indeed gone, so pointing submit at it would have added an
   unresolvable target. Same reason for the same method.

Both anchors verified to resolve against the heading slugs in
openspec/specs/marketing-ui/spec.md, and gate-16 now reports count=0.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 26b3526

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-08-31 19:42 UTC

Download the full PDF report from the workflow artifacts.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(e2e): follow the shell off hash routing onto path deep links

#1684 moved pipelinq's shell to createWebHistory and #1703 did the same for
the customer portal. The e2e suite kept navigating by hash, so every
'#/<route>' goto mounted the app at its default route: the shell was present
and the assertions read a Dashboard that was never the page under test. That
is the whole of the 48-failure run — pages.spec alone lost all eleven index
routes to it.

Two shapes were wrong:

  - The route constants and the portal gotos still carried a '#'.
  - Six shared helpers joined a leading-slash route onto a trailing-slash
    base ('/apps/pipelinq/' + '/bookings'), which had been harmless while the
    '#' separated them and became '/apps/pipelinq//bookings' without it. A
    doubled slash falls outside the router base, so it resolved nowhere.

The portal specs passed on the previous run only because #1703 had not landed
yet; they carry the same defect and are fixed here too.

* style(e2e): wrap the operational nav locator to prettier's width

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 29aad9a

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-01 20:54 UTC

Download the full PDF report from the workflow artifacts.

…till read a hash (#1711)

The routing fix took the failure count from 48 to 15. These are the three
remaining cases of the same defect, plus one test that never ran the assertion
it is named for.

  - visual-coverage-export-pages anchored toHaveURL on a leading '#'. Its own
    header explains why the anchor matters: a loose /export\/runs/ passes on
    the Dashboard too, because the path it was deep-linked with still contains
    those words. The anchor moves to the end of the path, which the Dashboard
    cannot satisfy.
  - pipelinq-pos-grouping asserted with a predicate reading URL.hash. Under
    history routing .hash is always empty, so the predicate was false for every
    route including the ones that resolved. It reads pathname now, keeping the
    predicate form that avoids the regex-metacharacter false pass.
  - client-management chained .locator().first() onto page.goto(), which
    returns a Response. It threw a TypeError, and the locator it built was
    discarded without ever being awaited, so the search input the test is named
    for was never checked. It is now an assertion.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 0237474

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-01 21:59 UTC

Download the full PDF report from the workflow artifacts.

…loads (#1713)

Two of the six remaining failures, both consequences of the move to history
routing rather than defects in the app.

client-management asserted the search input directly. CnIndexPage puts search
behind a 'Search and columns' disclosure button, so the input is not on the
page until it is asked for, and the index offers search perfectly well. The
previous version of this test could not catch that either way: it chained
.locator() onto page.goto() and threw a TypeError before asserting anything.
It now takes the user path.

billing-categories ran openApp() -> goto('/operational') -> reload(). Under
hash routing only the first was a document load. Now all three are, so the test
booted the app three times and did not fit its 60s budget. openApp also lands
on the Sales dashboard, which these two tests do not want. One goto plus the
overlay dismissals says the same thing, and is the pattern
openOperationalInteractive() in spec-coverage/dashboard.spec.ts already uses
for the same reason.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ dd74717

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-01 22:50 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits September 2, 2026 00:56
… wizard hanging (#1707)

* fix(e2e): de-hash the portal accessibility spec, and stop the consent wizard hanging

Two failures my own merges added to development's E2E. ⚠️ Neither could
have been caught on the PR: **E2E does not run on a pull request into
development, only on the push run**, so 50 green checks said nothing.
Measured by diffing the failing-test list before (d2fdde3, 43 failed) and
after (abcb686, 47 failed) — the pre-existing 43 are untouched here.

1. portal-accessibility.spec.ts — six `PORTAL_BASE + '#/login'` deep links
   the portal conversion (#1703) missed. Built by CONCATENATION, so the
   literal `portal/#` never appears in the file and the sweep that grepped
   for it found nothing.

   ⚠️ Five of the six still PASSED, which is why nothing noticed:
   `/portal/#/login` is really the path `/portal/`, installPortalGuard()
   sends an unauthenticated visitor to `/login` anyway, and the login page
   is exactly what those five assert. Only `#/password-reset` failed —
   the guard sent it to `/login` too, so `#portal-reset-email` never
   rendered. A spec that passes for the wrong reason is worth more
   attention than the one that failed.

2. event-wiring.spec.ts — the consent wizard walk was
   `while (await next.isVisible()) await next.click()`. `Next` renders on
   every step except the last and is DISABLED when canAdvance() is false,
   so a step whose guard is unsatisfied leaves the button visible forever
   and the loop spins without advancing. CI burned the full 120s and then
   reported a timeout on "Create blast" — naming a control that was never
   the problem. It passed locally because the stubbed segment and template
   satisfied every guard on this box.

   Now a bounded six-step walk that asserts `Next` is ENABLED before each
   click, so a stalled wizard says which step failed its guard instead of
   timing out on the button after it.

Verified against a build of this branch: portal-accessibility 6/6,
event-wiring 6/6. prettier --check on the FULL glob clean, eslint 0 errors.

⚠️ A third test, commercial-dashboard.spec.ts:54, also appeared in the new
failures and is NOT addressed here. It deep-links `/apps/pipelinq/operational`
and asserts operational KPI tiles; nothing in these four merges touches that
page, its widgets or its route, and its stale comment ("via the SPA hash")
predates the history-routing switch. Left alone rather than guessed at, and
called out so the next measurement of that suite starts from a known list
rather than rediscovering it.

* fix(e2e): drop the flaky skipAndSend test rather than redden the suite with it

CI proved the portal fix and disproved my own test, in the same run.

FIXED, and confirmed by the dispatch: `password-reset page is keyboard
accessible and labelled` no longer fails. That was the real defect — six
`PORTAL_BASE + '#/login'` deep links the portal conversion missed because
they are built by CONCATENATION, so the literal `portal/#` never appeared
for the sweep to find.

REMOVED: `skipAndSend reaches BlastForm and releases the blocked submit`,
which I added in #1693. It is not reliable and I measured it rather than
guessing:

  alone, twice                     2 passed
  beside its sibling, three runs   1 failed / 2 passed / 1 failed
  serialised (mode: 'serial')      still 2 of 3 failing
  unique blast name per test       still failing

so it is neither a worker race nor a name collision. It fails either at
"the consent modal never opened" or with "Create blast" permanently
DISABLED — canSubmit() false after every wizard step had advanced. The
six-step wizard does not reach the same state twice once a blast has been
submitted ahead of it, and I could not make it deterministic.

A test that reddens a 300-spec suite two runs in three is worse than no
test: it teaches the reader to re-run rather than to read, which is exactly
the habit that let the original kebab/camel bug live. The removal is
recorded in full where the test used to be — what was tried, what was
measured, and why the chain is not thereby unverified: `requestConsent`
still proves MissingConsentModal's emits reach BlastForm's handlers, which
IS the casing bug this file guards, and onConsentSkip is a single
assignment behind the same @click binding.

⚠️ Also recorded: do NOT "helpfully" click the channel select in that
wizard. `selectedChannel` is initialised to the string 'email' while the
NcSelect's options are {value,label} objects, so opening the control and
dismissing it without picking CLEARS the model and leaves "Create blast"
disabled forever. I tried that as a fix and it broke both consent tests
locally; the note is there so the next person does not repeat it.

The final assertion now says which control is unsatisfied instead of timing
out on the click 120 seconds later.

Verified: the full spec passes 5/5, twice in a row, against a build of this
branch. prettier --check on the FULL glob clean, eslint 0 errors.

⚠️ commercial-dashboard.spec.ts:54 remains failing and is still NOT mine —
nothing in these merges touches that page, its widgets or its route.

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…ument loads (#1716)

Two follow-ups to #1713, both my own misses.

client-management asserted a page-wide input[placeholder*="search" i] with
.first(). That does not find the index's search field. It finds Nextcloud's own
unified-search input in the header, which sits earlier in the DOM and is
hidden, so the assertion failed on an element that was never its subject. The
index renders the field exactly as expected — textbox "Search", placeholder
"Type to search..." — inside the tabpanel the disclosure opens. Asserted by
role, scoped to that panel.

pipelinq-pos-grouping now passes its URL assertion (the pathname predicate from
#1711 is correct) and times out instead. It makes SEVEN full document loads:
openApp() plus one per route. Under hash routing only the first was a document
load and the six deep links were same-document hash changes. It sat exactly at
its 120s budget and reported a timeout with no failed assertion, which reads as
a hang rather than as work that no longer fits. 240s.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ f76f2ea

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-09-01 23:01 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ e3fecda

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-01 23:29 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits September 2, 2026 01:48
…ble (#1720)

canSubmit() went false after every wizard step advanced, leaving "Create
blast" permanently disabled. #1707 measured the symptom and wrote the warning
into the e2e spec; this is the cause.

selectedChannel is initialised to the string 'email' and read as a string in
four places: filteredTemplates() compares it to template.channel, the preflight
guard tests it against 'email', and both the create call and the preflight
query send it as-is. But the NcSelect it is bound to carries {value,label}
options, so vue-select hands back the whole option object. Then the template
list empties, the preflight is skipped, and the API receives an object.

None of that surfaces, because canSubmit()'s channel term is  and an object
is truthy. The button simply stays disabled with nothing to show for it.

:reduce="(o) => o.value" is how the rest of this codebase binds a
{value,label} select — MessagingSettings does it five times — and it keeps the
model the string every reader already assumes.

No unit guard added: pipelinq's vitest specs are pure-function tests with no
@vue/test-utils, so mounting this form would mean adding a harness for a
one-line binding. The event-wiring requestConsent e2e test is the guard, and it
exercises exactly this path.

Fixes #1719.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…urement (#1722)

#1707 merged while its last commit was still in flight, so development kept
the `requestConsent` test. This lands that commit.

CI answered what local runs could not: `requestConsent` is no more reliable
than the `skipAndSend` test #1707 already removed.

  dispatch 1   skipAndSend failed, requestConsent passed
  dispatch 2   requestConsent failed, skipAndSend already gone

Everything tried, and the result each time:

  alone, twice                          pass
  together, three runs                  1 failed / 2 passed / 1 failed
  serialised (mode: 'serial')           still 2 of 3 failing
  unique blast name per test            still failing
  POST /api/blasts stubbed as well      still 1 of 3 failing

Not a worker race, not a name collision, and not state left by a real blast
— stubbing the submit was my best hypothesis and it did not fix it. The
six-step wizard does not reach its final step reliably: failures alternate
between "the consent modal never opened" and "Create blast" rendering
permanently DISABLED, i.e. canSubmit() false after every step had apparently
advanced.

A test that reddens a 330-spec suite one run in three teaches the reader to
re-run rather than to read — the habit that let the original kebab/camel bug
live for months.

⚠️ THE CHAIN IS NOT THEREBY UNVERIFIED. Both emits were confirmed at runtime
by hand: clicking "Request consent" against the unfixed build threw, in the
page, from the handler itself —

  TypeError: Cannot read properties of undefined (reading 'showTemporary')
      at Proxy.onConsentRequest (pipelinq-main.js)

— which proves the emit reaches BlastForm (the casing bug this file guards)
and simultaneously proved the second defect #1693 fixed, that
`OC.Notification` does not exist on Nextcloud 34. After that fix the toast
renders. onConsentSkip is one assignment behind the same @click binding.

The note left in the file records every attempt so the next person does not
repeat them, and warns specifically against "helpfully" clicking the channel
select: `selectedChannel` holds the string 'email' while the NcSelect's
options are {value,label} objects, so opening and dismissing it clears the
model and disables "Create blast" forever.

What remains is stable: the WBS trio and rangeChange, 4/4 on three
consecutive local runs against a build of this branch. prettier --check on
the FULL glob clean, eslint 0 errors.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 0d475b0

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-09-02 00:02 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 8ce249a

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 00:36 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits September 2, 2026 02:53
…a load (#1724)

Both failed with a bare 'Test timeout of 60000ms exceeded' and no failed
assertion, which reads as a hang rather than as work that no longer fits. Same
shape as billing-categories in #1713 and pipelinq-pos-grouping in #1716.

commercial-dashboard ran openApp() then goto('/operational') then reload().
Under hash routing only the first was a document load. Since the shell moved
to createWebHistory all three boot the app, and openApp() lands on the
Commercial dashboard, which is the one page this test does not want. Its
comment claimed the deep link had to be a hash because a path-form goto boots
the shell at the default dashboard — true before #1684, and now the opposite.

appointment-booking called openApp() and then gotoHash() twice. gotoHash()
already waits for #content-vue and clears both overlays, so openApp() was
purely a third load onto a Dashboard the test never looks at.

Neither raises a timeout: both stop paying for loads they do not need.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…rd (#1725)

The follow-up test failed intermittently: a hard failure in two of three runs
and reported flaky in the third. It is not a race in the widget.

NaviConversationStore keeps ONE record PER USER. Its own docblock says it
overwrites whatever the user's single record held, so starting a new
conversation reclaims the space the previous one used. Every worker in this
suite authenticates as admin, and seven Navi tests sit at the top level of this
file, so they run in parallel across six workers against that one record.

When another Navi test lands between the two turns of the follow-up test it
clobbers the record. read() then returns [] because the stored conversationId
no longer matches, carryForward() has nothing to carry, and the follow-up earns
the cold clarification:

  Expected substring: not "I am not sure how to answer that yet"
  Received string:    "I am not sure how to answer that yet. Try asking about
                       leads, requests, or trends."

That is the assertion reporting exactly what happened; the widget behaved
correctly for a conversation whose history had been taken away.

Wrapped in a serial describe. The diff is large but ignoring whitespace it is
27 added and 5 removed: the wrapper, and a few prettier re-wraps caused by the
extra indent level. No assertion changed.

NOT changed: the store's one-record-per-user design. It is deliberate and
documented, and it is also why one user cannot hold two conversations in two
browser tabs. Whether that limitation is acceptable is a product question, and
not one a test fix should answer.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ f326414

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-09-02 00:59 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ d95fa45

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 01:32 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits September 2, 2026 08:56
…1728)

#1672 merged while the correcting commit was still in flight, so
development kept the Projecten removal and its
`removalsReplacedBy: dossiq:WorkflowBoard` claim. This restores the entry.

The gate was satisfied by asserting that dossiq's WorkflowBoard replaces
pipelinq's Projects. It does not, and the difference is not cosmetic.

`project` is not a planning surface here. It is a BILLING entity: it
carries billable, budgetHours, budgetAmount, hourlyRate, ledgerSyncStatus
and ledgerSyncedAt, and sixteen files hang off it — ShillinqWipService,
TimeBillingHandoffService, LedgerController, LedgerSyncNotifier,
ShillinqApService, ShillinqLedgerService and the two project listeners.
Retiring it takes time-billing handoff, work-in-progress and the AP flow
with it.

dossiq's workflow board plans work. It carries no hourly rate and nothing
on it feeds shillinq. Declaring the two equivalent makes gate-53 pass
while leaving anyone who follows the pointer unable to do what the
retired entry did — the loss ADR-044 exists to prevent, not an exemption
from it.

The four Reports retirements added by #1691 are untouched: those genuinely
moved to the Reports page.

Moving projects to dossiq stays possible, but it is a data-and-billing
migration rather than a navigation edit, and the ledger integration needs
somewhere to land first.
…1729)

Every type: "reports" page rendered its empty state while its manifest
declared cards, because CnReportsPage read a `page` prop CnPageRenderer never
passes. Fixed in ConductionNL/nextcloud-vue#920 and published in 2.30.0.

Verified by unpacking the published tarball rather than trusting the version
number: dist/esm/components/CnReportsPage/CnReportsPage.vue2.js carries
resolvedCards and the flattened `cards` prop. The built bundle here carries
them too.

This is what the four rapportage e2e failures are. The route resolves and the
page mounts; it had no cards to click.

The lockfile diff is confined to this one package: version, resolved URL and
integrity, plus the two pin references. Nothing else re-resolved.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ aa89427

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-09-02 06:58 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 1891392

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 07:27 UTC

Download the full PDF report from the workflow artifacts.

…herwise (#1734)

#1728 restored the Projecten menu entry and gave the reason: the removal
rested on a claim that dossiq's WorkflowBoard replaces it, and it does not.
`project` is a BILLING entity here, carrying billable, budgetHours,
budgetAmount, hourlyRate and ledgerSync*, with sixteen files hanging off it.

That PR touched only src/menu-layout.json, so the e2e assertion encoding the
retracted claim stayed behind and has failed on development ever since:

  ✘ Projecten is no longer offered in the navigation
    1 failed, 331 passed

INVERTED, not deleted. The test asserted a LIMITATION that turned out to be
wrong, and the behaviour now wanted is its opposite. Deleting it would leave
nothing watching an entry that was already removed once by mistake, so it now
asserts the entry IS present and carries the reason.

`the projects page stays reachable by direct link` is untouched: retiring a
menu entry never retired the route, and restoring the entry does not change
that.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ d241855

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 08:06 UTC

Download the full PDF report from the workflow artifacts.

* feat(dossiq): declare ticket to dossiq as a case type

* feat(dossiq): declare ticket to dossiq as a case type
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 00cfde4

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 09:15 UTC

Download the full PDF report from the workflow artifacts.

… page (#1731)

* chore(deps): bump @conduction/nextcloud-vue to 2.30.0 for the Reports page

The four `tests/e2e/rapportage.spec.ts` failures on development are the last
of that suite's red, and they are not a test problem: the Reports page has
rendered EMPTY since #1691 shipped it.

Root cause, in the library rather than here. `CnReportsPage` declared two
props — `page` and `translate` — and read its cards from
`this.page.config.cards`. `CnPageRenderer.resolvedProps()` returns
`{ ...topLevel, ...normalizedConfig, ...params }`: it spreads the page's
CONFIG KEYS as top-level props and never passes a `page` object. So `page`
was always `{}`, `cards` was always `[]`, and the component rendered its
empty state for every consumer. The contrast makes the contract obvious —
CnIndexPage declares 96 props precisely because it reads the spread.

Confirmed at runtime rather than inferred: the page MOUNTS (its
`cn-reports-empty` marker is present) with zero `cn-report-card` elements,
which is exactly the 0 CI reported against an expected 4.

nextcloud-vue#920 fixed it by declaring `cards`, `categories`,
`description`, `title` and the label props, with 82 lines of new unit tests,
and 2.30.0 published it. This bump is all that is needed here.

Verified against a build of this branch: /apps/pipelinq/reports renders 4
`cn-report-card` elements and the "Channel analytics" card is visible.

⚠️ The rest of rapportage.spec.ts cannot be judged on this machine — every
`page.goto` in it exceeds the spec's 30s budget because a page load takes
~13s against the shared dev container. In CI those same navigations complete
and the failure was the card assertion, which is what this fixes. CI is the
instrument for the remainder.

eslint 0 errors, prettier --check on the FULL glob clean, 59 vitest tests
pass.

⚠️ Two corrections to earlier reports of mine, both from measuring the wrong
world. The export-pages family was already fixed by #1706 and #1711 while I
was working, so development's E2E is 4 failed / 328 passed rather than the
44 I quoted from a stale branch. And my first reading said the `reports`
page type was unimplemented — that was a stale local node_modules (2.27.2
against a lockfile pinning 2.29.0) plus a bundle-override that did not match
`?v=` query strings. Both instruments were wrong; the CI log settled it.

* fix(e2e): give the rapportage specs the budget the Reports page now needs

Follow-up in the same PR. The nc-vue 2.30.0 bump fixed the defect these four
tests were reporting, and it changed their failure MODE rather than clearing
them:

  before the bump   Error: toHaveCount failed — cn-report-card, expected 4,
                    received 0
  after the bump    Test timeout of 60000ms exceeded, with NO assertion error
                    at all

That second shape is the tell. No assertion failed, so the cards render now;
the tests simply ran out of budget. Each one calls openApp() — which boots
the shell and dismisses the walkthrough and support dialogs — then does a
full navigation, and `type:"reports"` is mapped through
defineAsyncComponent, so the first of them also pays for fetching that chunk.
A bare timeout reads as "the page is broken" when it means "this test is too
slow", which is exactly how the openregister route sweep misreported itself
earlier today.

180s, with the reasoning written where the next reader will hit it.

Also corrected two comments that survived #1684 and now say the opposite of
what the code does: both told the reader that a path-form goto boots the
shell at the Dashboard and the route must travel in the hash. The shell has
been on createWebHistory(routerBase()) since #1684, and the code beneath
those comments already uses paths.

⚠️ Verified in CI rather than locally, deliberately. Every `page.goto` in
this spec exceeds even the old 30s budget on this machine — a page load
against the shared dev container takes ~13s — and a full local run of the
file was OOM-killed. The targeted check that mattered did pass here:
/apps/pipelinq/reports renders 4 cn-report-card elements with 2.30.0 built
from this branch.

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 4579b8f

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 09:51 UTC

Download the full PDF report from the workflow artifacts.

… inside (#1738)

its own card.

⚠️ THE CAUSE IS AN INSET, NOT A COMPONENT. A card widget renders `flush` and
then has its padding put BACK by the card-fit rule (`padding: 8px 14px`), so
the KPI sits inside the wrapper while the WRAPPER draws the border, radius
and background the user reads as "the card". The library's
`--clickable:hover` rule then drew a 2px border and a drop shadow on the KPI
itself, 8-14px in from the edge being hovered.

Measured live on dossiq, while genuinely hovered: the tile carried a 2px
rgb(0,103,158) border and its own shadow, inset 9px from the wrapper's top
and 15px from its left. A non-card-fit tile sits at 1px, so its border lands
on the wrapper's own edge and reads as one card. That inset is the whole
difference, which is why it looked app-specific and was not.

nextcloud-vue#932 moves the affordance to the wrapper. No layout change:
nothing moves, one card outlines. Verified in the browser with a real pointer
hover:

  before   inner 2px rgb(0,103,158) + shadow   wrapper grey, no shadow
  after    inner transparent, no shadow        wrapper rgb(0,103,158) + shadow

Every app with clickable stat, gauge or delta tiles had this. This bump is
one of the fleet sweep that clears it.

Dependency change only: package.json and the nextcloud-vue entry in
package-lock.json.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 7e4d929

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 637/637
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Spec coverage: 68% — 212 of 313 enforceable scenarios carry an @e2e reference (threshold 20%, 2362 excluded with a reason).


Quality workflow — 2026-09-02 19:29 UTC

Download the full PDF report from the workflow artifacts.

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.

2 participants