Skip to content

Release: merge development into beta - #1621

Merged
rubenvdlinde merged 16 commits into
betafrom
development
Aug 31, 2026
Merged

Release: merge development into beta#1621
rubenvdlinde merged 16 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 09:50
The 0.4.12-beta.20260831094936 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.12-beta.20260831094936

chore(release): sync beta back into development
…1627)

Per-widget `valueColor` is applied as an INLINE style on the number, so it beats
the canonical card accent, and a hex literal opts the card out of NL Design
System theming — which kpi-card.css forbids outright, because the nldesign app
re-themes by overriding the Nextcloud tokens.

Two outcomes, no third:

  * a colour that merely restated the default accent is deleted — `#0082c9` IS
    `--color-primary-element` in the default theme, so the tile looks identical
    and now re-themes correctly;
  * a colour that carried meaning becomes `variant`, which is themed and drives
    the icon tint and the number together so the two cannot disagree.

`variant` on a stat/delta resolves through VARIANT_COLORS, which was moved onto
the `-text` tokens first (nextcloud-vue#888) — the plain fill tokens failed WCAG
AA at 1.08:1 as a foreground colour, and these conversions would have been the
first tiles to hit that path.

Edited as text rather than re-serialised, so the manifest keeps its hand-laid-out
formatting; every `valueColor` in this file was confirmed to sit on a KPI widget
first. Manifest schema validation passes.

Prepared in a fresh clone — the workspace checkout is held by other in-flight
work.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Dependabot cannot propose composer updates for this app. Its updater fails
with:

  Your requirements could not be resolved to an installable set of packages.
    - edgedesign/phpqa[v1.27.0, ..., v1.27.2] require ext-xsl * ->
      it is missing from your system.

config.platform pins php 8.3 so composer resolves against a known PHP version,
but says nothing about extensions. edgedesign/phpqa requires ext-xsl and the
resolving environment does not have it, so the resolve fails before any bump
can be computed.

CI is unaffected, which is why this went unnoticed: composer install replays
the committed lock and never re-resolves, so the pipeline stays green while
dependabot -- which does re-resolve -- fails every time. The shared quality.yml
installs no xsl extension anywhere and never invokes phpqa.

Declaring ext-xsl beside the php pin makes resolution assume exactly what the
committed lockfile already assumes. Verified in a clean composer:2 container
without --ignore-platform-reqs: the same file fails without this line and
resolves (103 installs, lock written) with it.

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

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 7d2da91

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 ✅ 631/631
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

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

Download the full PDF report from the workflow artifacts.

…et (#1625)

The component was not accidentally orphaned. ClientDetail and
ContactDetail both already carry `{ type: "integration", integrationId:
"email" }`, which the library serves with CnEmailTab against
`/objects/{register}/{schema}/{id}/emails` — the same linked emails this
component was written to show. It was replaced, and the replacement is
the shared, maintained surface.

Its own `fetchEmails()` never made that request: it set `emails = []`,
so mounting it would have rendered "No emails linked to this entity"
forever. Re-mounting it would have put a second, permanently empty email
panel beside a working one.

Deleting rather than fixing, because fixing it produces a duplicate of a
surface that already works. The archived change that specced it stays
archived; the behaviour it asked for is delivered by the integration
widget.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 04bfce2

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 ✅ 631/631
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 11:07 UTC

Download the full PDF report from the workflow artifacts.

)

* test(leads): pin the pipeline select end to end

The pipeline dropdown was empty on both create forms and nothing caught
it. tests/vitest/pipelineScope.spec.js now pins the filter predicate, but
that was only half the defect: `propertyMappings` was undeclared on the
schema, so the data the predicate reads was discarded on write. Only an
end-to-end check spans both halves.

Fields are addressed by data-testid rather than label text. The dev
instance renders Dutch ("Pijplijn" / "Fase") and CI renders English, so a
label selector passes in one and fails in the other for no product
reason.

* style(e2e): prettier on the pipeline-select spec

* feat(forms): create a client inline, then pick a contact under it

The lead and request forms could only pick a client that already
existed, and never offered the contact at all — even though both
schemas have carried a `contact` property all along. Creating the
client meant leaving the form, making it elsewhere, and starting over.

Both forms now use CnResourceSelect for the pair. Client can create
what it cannot find; contact is scoped to the chosen client and stays
disabled until there is one, the same cascade Stage already has on
Pipeline. Changing the client clears a contact that no longer belongs
under it.

The create hook opens the FULL dialog rather than saving the typed
term. `client` and `contact` both mark `contactsUid` required and it is
minted server-side, so a bare name cannot satisfy either schema — the
picker hands control to the dialog and resumes with whatever it
resolves, or nothing when cancelled. That also finally mounts
ContactForm, which carried a note that no importer, manifest page or
router entry referenced it, leaving it unreachable on every instance.

Dashboard buttons move into the manifest, which is where placement
belongs. `DashboardHeaderActions` hardcoded New Lead + New Request +
New Client in one component, so both dashboards naming it got all
three, and New Request sat on the sales dashboard. Each page now
declares its own actions: Sales and Operational get New Lead and New
Client, Customer Support gets New Request. The component is deleted and
its bespoke Refresh with it — `showRefresh` now drives the built-in
one, which works because dashboardData subscribes to `cn:page:refresh`.

`open-modal` rather than `open-form`: the generic schema-driven dialog
cannot express a picker that creates its own options or a cascade
between two fields. Placement is declarative; only the form body stays
app-owned. Registry modals are mounted by CnAppRoot, which forwards
`close` and nothing else, so each dialog now routes to its own detail
page instead of relying on a parent to do it.

Verified against the running instance: New Request is gone from Sales
and present on Customer Support; the client picker lists 12 clients
without typing and offers Create "Bakkerij" for an unknown name; and
selecting Bakkerij scopes the contact picker to exactly its two
contacts rather than all four in the system.

* fix(l10n): translate the four new picker strings

The client and contact pickers introduced four user-facing strings that
were never registered: two placeholders, the disabled-state hint, and
the contact create failure. CI caught them, which is the gate working.

Dutch is filled in rather than left to fall back to English, because the
dev instance runs Dutch and a half-translated form is how a screen ends
up mixed-language.

The schema description added alongside the union-type fix was missing
from en.json too, and the browser catalogues are rebuilt.

* test(forms): pin the client create and contact cascade end to end

Three behaviours that only exist end to end: the client list is
browsable without typing, an unknown name offers Create rather than
dead-ending on "no results", and the contact list is SCOPED to the
chosen client.

Scoping is the half a unit test cannot see. The filter is applied by the
shared component against a live query, so "shows the right contacts" and
"shows every contact in the system" are indistinguishable without real
data behind them.

Two things the first drafts got wrong, both now encoded in the spec:
clicking the first option matching the typed term can select the
synthetic Create entry and open the create dialog instead of choosing
the client, so the match filters it out with hasNotText; and the click
must be asserted to have TAKEN before the cascade is judged, otherwise a
failed selection reports as a broken cascade.

Verified against the running instance: 3 passed.

* chore(deps): @conduction/nextcloud-vue 2.27.0 -> 2.27.1

Picks up the fix that closes a registry modal on route change. Without
it a create dialog opened from a dashboard stays mounted over whatever
page you navigate to next, which is reachable from the buttons this
branch adds.

Verified by unpacking the published tarball rather than trusting the
version number: the route watcher is in CnAppRoot and the manifest
schema reads 2.27.0 with createOverride declared.

* spec(leads): specify linked-party selection, and move the request test with it

Gate-16 flagged 23 changed methods with no @SPEC. They implement a real
feature, so they get a real requirement rather than 23 excludes: the
lead-management spec now states that the client field offers existing
clients without a search term and offers to create one that does not
exist, that creating opens the FULL form because `contactsUid` is
server-provisioned and a name alone cannot satisfy the schema, and that
the contact field is scoped to the selected client and unavailable
before one is chosen.

Two existing e2e tests asserted New Request on the sales dashboard,
which is exactly what this branch changes, so they failed for the right
reason. Both now assert it is ABSENT there, and a new test asserts it is
present on Customer Support — the placement is pinned from both sides
rather than simply un-asserted.

* chore(lint): clear the last warnings, with the reason at each site

Takes the backlog to 2, and those two are EmailTimeline's, which #1625
deletes.

All thirteen are deliberate, so each carries its reason rather than a
bare suppression. Seven boolean props default ON because they are
opt-OUT flags: a form draws its own action bar unless a host supplies
one, a wedge scanner must own focus or the first scan is lost, purchase
history is long enough to start collapsed. Inverting those names would
make every ordinary call site pass a negative prop to get normal
behaviour. Six widget `title` props are declared but not rendered,
because declaring them is what stops Vue's attribute fallthrough
painting `title="…"` onto the root element and hovering a tooltip over
the entire widget — removing them to satisfy the linter reintroduces
that bug.

Placement matters more than it looks. `vue/no-boolean-default` reports
on the `default: true` LINE, not the prop, so a directive above the prop
suppresses nothing and eslint then flags it as unused — the same
one-line-off mistake that had been hiding the v-html XSS. A multi-line
reason above the directive breaks it the same way, so each reason sits
above and the bare directive last.
The 0.4.13 release bumped the version on main. Without this,
development stays behind main and the next development -> main 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.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 12f78cc

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 ✅ 631/631
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 11:13 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits August 31, 2026 13:14
….4.13

chore(release): sync main back into development
Levels this app with the fleet. 2.27.2 adds two fixes the earlier 2.27.0 pin does
not carry:

  * headerless is no longer chromeless — a flat KPI card in a borderless wrapper
    had no card, border or background at all;
  * a stat `variant` paints from the `-text` tokens rather than the fill tokens,
    which failed WCAG AA at 1.08:1 as a foreground colour. The KPI colour
    cleanup converted hardcoded values to `variant`, so this is what makes those
    conversions contrast-safe.

Lockfile only, and npm pruned nothing.

Prepared in a fresh clone — the workspace checkout is held by other in-flight
work.

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

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 8a38f57

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd ⏭️
psalm ⏭️
phpstan ⏭️
phpmetrics ⏭️
eslint ⏭️
stylelint ⏭️
build ⏭️
composer ⏭️ ⏭️
npm ⏭️ ⏭️
app:check-code ⏭️
info.xml ⏭️
REUSE ⏭️
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-31 11:26 UTC

Download the full PDF report from the workflow artifacts.

dependabot Bot and others added 3 commits August 31, 2026 13:49
Bumps [postcss-html](https://github.com/ota-meshi/postcss-html) from 1.8.1 to 2.0.0.
- [Release notes](https://github.com/ota-meshi/postcss-html/releases)
- [Commits](ota-meshi/postcss-html@v1.8.1...v2.0.0)

---
updated-dependencies:
- dependency-name: postcss-html
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* test(leads): pin the pipeline select end to end

The pipeline dropdown was empty on both create forms and nothing caught
it. tests/vitest/pipelineScope.spec.js now pins the filter predicate, but
that was only half the defect: `propertyMappings` was undeclared on the
schema, so the data the predicate reads was discarded on write. Only an
end-to-end check spans both halves.

Fields are addressed by data-testid rather than label text. The dev
instance renders Dutch ("Pijplijn" / "Fase") and CI renders English, so a
label selector passes in one and fails in the other for no product
reason.

* style(e2e): prettier on the pipeline-select spec

* feat(forms): create a client inline, then pick a contact under it

The lead and request forms could only pick a client that already
existed, and never offered the contact at all — even though both
schemas have carried a `contact` property all along. Creating the
client meant leaving the form, making it elsewhere, and starting over.

Both forms now use CnResourceSelect for the pair. Client can create
what it cannot find; contact is scoped to the chosen client and stays
disabled until there is one, the same cascade Stage already has on
Pipeline. Changing the client clears a contact that no longer belongs
under it.

The create hook opens the FULL dialog rather than saving the typed
term. `client` and `contact` both mark `contactsUid` required and it is
minted server-side, so a bare name cannot satisfy either schema — the
picker hands control to the dialog and resumes with whatever it
resolves, or nothing when cancelled. That also finally mounts
ContactForm, which carried a note that no importer, manifest page or
router entry referenced it, leaving it unreachable on every instance.

Dashboard buttons move into the manifest, which is where placement
belongs. `DashboardHeaderActions` hardcoded New Lead + New Request +
New Client in one component, so both dashboards naming it got all
three, and New Request sat on the sales dashboard. Each page now
declares its own actions: Sales and Operational get New Lead and New
Client, Customer Support gets New Request. The component is deleted and
its bespoke Refresh with it — `showRefresh` now drives the built-in
one, which works because dashboardData subscribes to `cn:page:refresh`.

`open-modal` rather than `open-form`: the generic schema-driven dialog
cannot express a picker that creates its own options or a cascade
between two fields. Placement is declarative; only the form body stays
app-owned. Registry modals are mounted by CnAppRoot, which forwards
`close` and nothing else, so each dialog now routes to its own detail
page instead of relying on a parent to do it.

Verified against the running instance: New Request is gone from Sales
and present on Customer Support; the client picker lists 12 clients
without typing and offers Create "Bakkerij" for an unknown name; and
selecting Bakkerij scopes the contact picker to exactly its two
contacts rather than all four in the system.

* fix(l10n): translate the four new picker strings

The client and contact pickers introduced four user-facing strings that
were never registered: two placeholders, the disabled-state hint, and
the contact create failure. CI caught them, which is the gate working.

Dutch is filled in rather than left to fall back to English, because the
dev instance runs Dutch and a half-translated form is how a screen ends
up mixed-language.

The schema description added alongside the union-type fix was missing
from en.json too, and the browser catalogues are rebuilt.

* test(forms): pin the client create and contact cascade end to end

Three behaviours that only exist end to end: the client list is
browsable without typing, an unknown name offers Create rather than
dead-ending on "no results", and the contact list is SCOPED to the
chosen client.

Scoping is the half a unit test cannot see. The filter is applied by the
shared component against a live query, so "shows the right contacts" and
"shows every contact in the system" are indistinguishable without real
data behind them.

Two things the first drafts got wrong, both now encoded in the spec:
clicking the first option matching the typed term can select the
synthetic Create entry and open the create dialog instead of choosing
the client, so the match filters it out with hasNotText; and the click
must be asserted to have TAKEN before the cascade is judged, otherwise a
failed selection reports as a broken cascade.

Verified against the running instance: 3 passed.

* chore(deps): @conduction/nextcloud-vue 2.27.0 -> 2.27.1

Picks up the fix that closes a registry modal on route change. Without
it a create dialog opened from a dashboard stays mounted over whatever
page you navigate to next, which is reachable from the buttons this
branch adds.

Verified by unpacking the published tarball rather than trusting the
version number: the route watcher is in CnAppRoot and the manifest
schema reads 2.27.0 with createOverride declared.

* spec(leads): specify linked-party selection, and move the request test with it

Gate-16 flagged 23 changed methods with no @SPEC. They implement a real
feature, so they get a real requirement rather than 23 excludes: the
lead-management spec now states that the client field offers existing
clients without a search term and offers to create one that does not
exist, that creating opens the FULL form because `contactsUid` is
server-provisioned and a name alone cannot satisfy the schema, and that
the contact field is scoped to the selected client and unavailable
before one is chosen.

Two existing e2e tests asserted New Request on the sales dashboard,
which is exactly what this branch changes, so they failed for the right
reason. Both now assert it is ABSENT there, and a new test asserts it is
present on Customer Support — the placement is pinned from both sides
rather than simply un-asserted.

* feat(contacts): the contact form can create the client it needs

ContactForm's client field was a bespoke NcSelect with its own
debounced search, its own initial-page load, and an
ensureClientInOptions() helper that re-fetched a client already on the
record when it fell outside the current results. All three now come
from CnResourceSelect, so the field behaves the same as the one on the
lead and request forms and about seventy lines of plumbing go away.

It also gains what it lacked: creating a client from the typed name. A
contact belongs to a client, so making one for a client the system does
not have yet was previously a dead end.

Create is disabled when `preSelectedClient` is set. That is the path
taken when this form is opened from the contact picker on a lead or
request, where the client is already chosen and creating another would
make no sense.

Stacked on the picker work: the "Select or create a client" string is
registered there.

* chore(lint): clear the last warnings, with the reason at each site

Takes the backlog to 2, and those two are EmailTimeline's, which #1625
deletes.

All thirteen are deliberate, so each carries its reason rather than a
bare suppression. Seven boolean props default ON because they are
opt-OUT flags: a form draws its own action bar unless a host supplies
one, a wedge scanner must own focus or the first scan is lost, purchase
history is long enough to start collapsed. Inverting those names would
make every ordinary call site pass a negative prop to get normal
behaviour. Six widget `title` props are declared but not rendered,
because declaring them is what stops Vue's attribute fallthrough
painting `title="…"` onto the root element and hovering a tooltip over
the entire widget — removing them to satisfy the linter reintroduces
that bug.

Placement matters more than it looks. `vue/no-boolean-default` reports
on the `default: true` LINE, not the prop, so a directive above the prop
suppresses nothing and eslint then flags it as unused — the same
one-line-off mistake that had been hiding the v-html XSS. A multi-line
reason above the directive breaks it the same way, so each reason sits
above and the bare directive last.
…1634)

Bumps [node-polyfill-webpack-plugin](https://github.com/Richienb/node-polyfill-webpack-plugin) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/Richienb/node-polyfill-webpack-plugin/releases)
- [Commits](Richienb/node-polyfill-webpack-plugin@v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: node-polyfill-webpack-plugin
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 333a561

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd ⏭️
psalm
phpstan ⏭️
phpmetrics ⏭️
eslint
stylelint
build ⏭️
composer ⏭️ ⏭️
npm ⏭️ ⏭️
app:check-code ⏭️
info.xml ⏭️
REUSE ⏭️
PHPUnit
Newman
Playwright
Hydra gates

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

Download the full PDF report from the workflow artifacts.

CnAppRoot warned three times on every page load:

  [CnAppRoot] Registry entry "LeadCreateDialog" (kind: "modal") is
  missing required metadata field "propsSchema".

The ADR-036 validator wants the field on every registry entry, and the
modals added with the declarative header actions did not carry it. The
widget entries in this file already set `propsSchema: null` for the same
reason; these now match.

Found by reading the browser console after the merge, not by any gate —
CnAppRoot warns rather than throws, so it is invisible to CI.
The lead side was already covered. The request form is a separate
component that happens to share the pattern, so a change applied to one
and not the other would leave this half broken with every lead-side test
still green.

That is not hypothetical: the pipeline-select defect this all started
from lived in BOTH forms, and only the lead one was ever looked at.

Covers the same three behaviours from Customer Support: browse clients
without typing, offer Create for an unknown name, and scope the contact
picker to the chosen client. Verified against the running instance:
2 passed.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 56916b3

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd ⏭️
psalm ⏭️
phpstan ⏭️
phpmetrics ⏭️
eslint ⏭️
stylelint ⏭️
build ⏭️
composer ⏭️ ⏭️
npm ⏭️ ⏭️
app:check-code ⏭️
info.xml ⏭️
REUSE ⏭️
PHPUnit
Newman
Playwright
Hydra gates

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

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ 6dd3bd4

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 12:57 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/pipelinq @ d0ef636

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 13:29 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit baac27b into beta Aug 31, 2026
99 checks passed
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