Release: merge development into beta - #563
Conversation
Clicking a row now opens the secret in an NcAppSidebar over the vault list instead of navigating to a separate page, so the list context (folder, search, pagination) stays visible and intact behind it. The detail page is gone: the id rides as an optional segment on the list routes (/secrets/:id? and /folders/:folderId/:id?), because CnPageRenderer keys its render on the page id and a separate page remounted the list on every open. Deep links to #/secrets/<id> keep resolving (list + open sidebar) and stay behind the vault-lock guard. Route <-> sidebar mapping lives in src/utils/detailRoute.js. The sidebar lays the detail out Proton Pass style: the action row (labeled Edit opening the edit dialog, icon-only Share with ariaLabel, and a "Secret actions" menu holding Move, Delete and Close - the native X is hidden while the row renders; Esc still closes everywhere) sits with the title in the header; fields render as grouped icon/label/value boxes with a visible attachments box and a last-modified/created metadata box (NcDateTime); the informational tail folds into "More information" (sharing, requests, activity) and owner-only "Advanced" (versions, rotation, honey) disclosures. Card and identity secrets render their payloads as first-class rows (masked number/CVV/PIN/BSN with eye + copy, revealed numbers regrouped in blocks of four, identity split into Personal/Address/Contact sections); the old CardDisplay/IdentityDisplay components are deleted and the raw key row is hidden for composite types. The pane widens to clamp(300px, 35vw, 560px) above the library's mobile breakpoint. Decryption semantics are unchanged. Edit/Move refresh the list behind the sidebar; Delete closes it. The SecretDetail vitest specs are retargeted at the sidebar component; the new route mapping has its own spec; e2e drives Move/Share through the new action row and opens the More-information disclosure for the activity assertion; the guards spec drops the removed page id.
New source strings from the sidebar restyle - "Details", "Versions", "Secret actions", "More information", and the identity section headings "Personal details" / "Address details" / "Contact details" - translated into English plus all 36 required locales, with the .js catalogs regenerated and the parity ratchet tightened to record the progress.
Opening the folder the "..." stand-in node represents lit TWO rows in the rail: the node itself (vue-router exact-active styling on its link) and its parent, which received the deepest-visible-ancestor highlight. The ancestor rule now returns nothing when the active folder is the single hidden child the "..." stands for, so exactly one row reads as selected. Navigation through the node is unchanged, and the multi-hidden-children case keeps highlighting the parent row as before.
Navigating to another folder used to keep the previous folder's rows on screen until the new ones landed, then swap them mid-air - CnIndexPage only shows its full loading spinner while it is loading AND has nothing to render. A folderSwitching flag now blanks the list for the duration of the navigation's fetch, so a switch reads as spinner -> new folder's contents. The flag starts true because root <-> folder navigations REMOUNT the view (the routes are different manifest pages and CnPageRenderer keys its render on the page id), and a fresh mount rendered the store's previous rows before mounted() fetched anything - the folderId watcher only covers folder -> folder moves. In-folder refreshes and searches keep the in-place behavior.
A sweep over every locale for values identical to their English source
found six bulk-copied strings: "Applications awaiting approval", "No
applications awaiting approval" (31 locales), "Your secrets, folders
and applications at a glance", "Vault overview", "Rotation due" (33
each) and "View all" (31). All now carry real translations phrased
with each locale's established terminology. Script-mismatched
loanwords are also fixed: Token and Status become Cyrillic in mk/sr,
and Link becomes Länk/Ligação/Colliaziun in sv/pt/rm. Values that
already held a genuine translation were never touched, and legitimate
loanwords (URL, Token in Latin-script locales, Dashboard, Widgets,
Layout) and the brand string "Keepiq {version}" stay as they are. The
.js catalogs are regenerated.
The secret types come from the server with English labels seeded by SeedSecretTypes.php, and the frontend printed type.label raw - so the type filter, the create and edit dialogs' type selectors and the detail sidebar's type subline showed English in every language. A new secretTypeLabel() helper (src/utils/secretTypes.js) routes the label through the translator at all four sites, falling through unchanged for custom types without a catalog entry - the dynamic-key pattern the nav already uses for manifest menu labels. The ten system labels are translated in English plus all 36 required locales (295 new entries; seven keys were missing from every catalog), the .js catalogs are regenerated and the parity ratchet is tightened accordingly.
… l10n commit Commit 17988a7 accidentally carried a local development aid into LockScreen.vue: a DEV-ONLY auto-unlock block that reads the master password in plaintext from localStorage and unlocks the vault on page load, plus its status banner and data field. It was guarded by a NODE_ENV check (webpack strips it from production builds) and inert without an explicit localStorage opt-in, but it defeats the lock screen's security model and was never meant to be tracked. The same commit also dropped two @SPEC tags from handlePasskeyUnlock; they are restored. The file now matches its pre-17988a77 state exactly.
…p-row toolbar The vault list adopts CnIndexPage's own selection: selectable with rowClickToView (clicking opens the detail, checkboxes select), selectedIds fed from the bulk store and @select synced back. The table gets header select-all, row checkboxes and selected-row highlight; cards get a checkbox each; the list keeps shift-range selection through NcCheckboxRadioSwitch — the same Nextcloud checkbox the other views render — with a capture-phase shift recorder. One selection is shared across view switches, pruned to the visible rows on every list change (folder navigation, page flips, filters, refresh), and the strip's bulk buttons disable while the list is (re)loading so a stale selection from the previous page cannot be moved or deleted. Bulk actions live in the library's contextual selection strip alone (live count announced via role="status" — WCAG 2.1 SC 4.1.3 — plus Move, Share, Add to team folder and Delete with icons, and the strip's own Clear; bulk-actions §3.1's required bar). The Actions menu keeps only the Select-all checkbox — the sole whole-view select-all for the list and card views. Selected list rows tint the item with its hover token, never the checkbox gutter. Folders left the collection: vaults/subfolders render as a captioned strip (Vaults at root / Folders inside) above the collection in every view mode (the library's #before-collection slot), so they no longer masquerade as secrets in table/cards and survive pagination; the strip owns the single divider. Breadcrumbs render below the actions bar, above that strip, a step larger — the trail is the folder heading now that the page title is visually hidden (the h1 stays for accessibility). The bar owns the top row: keepiq's doubled page padding is dropped and the bar clears the floating nav toggle with a 30px !important margin — a margin so its background starts after the toggle, !important because design-system themes flatten the bar's box with their own !important rules. Keepiq-scoped, as is the container-scale rounding; other apps keep their heading and the library defaults. The secret-type filter is a funnel button beside the search field carrying the sort radios too; it flips to its filled glyph in the primary color whenever a type filter is set or the sort deviates from the default name sort. New l10n string "Filter and sort" in en + all 36 required locales. e2e: clickOverflowAction opens the bar menu via a new openActionsMenu helper; export-gdpr's four inline menu openers use it.
… display cap Being in a folder deeper than the rail renders (e.g. test11 under the 5-level cap) highlighted the deepest visible ancestor (test5), which read as the wrong folder being open. The active trail is computed once (activeFolderTrail); when it exceeds the cap no ancestor row lights up and the "…" node under the deepest visible branch renders active instead (ellipsisHighlightId, passed into NavFolderTree's ellipsis item) — exactly one row selected, and it is the row that stands for the hidden chain. This subsumes the earlier single-hidden-child special case.
ImageMagick's internal SVG renderer — used by Nextcloud theming to generate the favicon — does not draw stroked paths, so the shackle vanished from generated favicons. All five icons (app, app-dark, app-store, pwa-icon, pwa-icon-maskable) redraw the shackle as a filled outline of equivalent thickness, so stroke-less rasterizers render the full lock.
CI's stylelint (declaration-empty-line-before) rejects a blank line between a custom property and the following declaration — a formatting habit that is valid in nextcloud-vue's stylelint config but not in keepiq's. The full stylelint sweep is green again, and RESTYLE-PLAN's per-stage verification list now names stylelint so the gate stops being skippable locally.
gate-16: @SPEC tags on the seven changed methods the gate named — onRowCheck/onSelectAll (bulk-actions multi-select requirement), the selectedFolderId watcher (folder management), typeFilterOptions and both secret dialogs' typeOptions (secret types), and the detail sidebar's mounted() (read secret). gate-40: the list checkbox's ariaLabel binding becomes aria-label — identical Vue behaviour, and the form-label rule recognises the kebab form. gate-45: the sidebar's accordion-chevron transition gains the prefers-reduced-motion fallback the rest of the file already has. gate-46: five @SPEC tags pointed at anchors that never existed — requirement-secret-sharing is requirement-share-a-secret (user-sharing) and requirement-cached-vault-is-read-only is requirement-offline-mode-is-strictly-read-only (offline cache).
Brings the released library features the vault page consumes, until now only available through a locally built dist overlaid into node_modules: the contextual selection strip (live role="status" count, #selection-actions slot, clear-selection wiring), the #after-search and #before-collection CnIndexPage slots, the role-grouped actions-bar layout with its narrow-width container-query tiers, clickToView on CnCardGrid/CnObjectCard, and the CnPageRenderer fix that stops lifted manifest fields (title, icon) falling through as HTML attributes on custom pages — the "Vault" tooltip that hovered over every custom page. With the package live, the overlay workflow is retired for this line of work: node_modules now matches the lockfile again.
…yword CI's stylelint (declaration-property-value-keyword-no-deprecated, in the newer toolchain the merged lockfile installs) rejects `word-break: break-word` — a deprecated keyword that always aliased overflow-wrap behaviour. The detail rows' value wrapping now uses `overflow-wrap: anywhere`, which wraps long secret values identically.
The 0.2.6-beta.20260831102714 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.
) 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.
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>
…2.6-beta.20260831102714 chore(release): sync beta 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.
Quality Report — ConductionNL/keepiq @
|
| 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:46 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 12:39 UTC
Download the full PDF report from the workflow artifacts.
…ection (#479) * feat(vault): secret details in a right sidebar over the list Clicking a row now opens the secret in an NcAppSidebar over the vault list instead of navigating to a separate page, so the list context (folder, search, pagination) stays visible and intact behind it. The detail page is gone: the id rides as an optional segment on the list routes (/secrets/:id? and /folders/:folderId/:id?), because CnPageRenderer keys its render on the page id and a separate page remounted the list on every open. Deep links to #/secrets/<id> keep resolving (list + open sidebar) and stay behind the vault-lock guard. Route <-> sidebar mapping lives in src/utils/detailRoute.js. The sidebar lays the detail out Proton Pass style: the action row (labeled Edit opening the edit dialog, icon-only Share with ariaLabel, and a "Secret actions" menu holding Move, Delete and Close - the native X is hidden while the row renders; Esc still closes everywhere) sits with the title in the header; fields render as grouped icon/label/value boxes with a visible attachments box and a last-modified/created metadata box (NcDateTime); the informational tail folds into "More information" (sharing, requests, activity) and owner-only "Advanced" (versions, rotation, honey) disclosures. Card and identity secrets render their payloads as first-class rows (masked number/CVV/PIN/BSN with eye + copy, revealed numbers regrouped in blocks of four, identity split into Personal/Address/Contact sections); the old CardDisplay/IdentityDisplay components are deleted and the raw key row is hidden for composite types. The pane widens to clamp(300px, 35vw, 560px) above the library's mobile breakpoint. Decryption semantics are unchanged. Edit/Move refresh the list behind the sidebar; Delete closes it. The SecretDetail vitest specs are retargeted at the sidebar component; the new route mapping has its own spec; e2e drives Move/Share through the new action row and opens the More-information disclosure for the activity assertion; the guards spec drops the removed page id. * l10n: detail-sidebar strings in every required locale New source strings from the sidebar restyle - "Details", "Versions", "Secret actions", "More information", and the identity section headings "Personal details" / "Address details" / "Contact details" - translated into English plus all 36 required locales, with the .js catalogs regenerated and the parity ratchet tightened to record the progress. * fix(nav): highlight only the "..." node when its hidden folder is open Opening the folder the "..." stand-in node represents lit TWO rows in the rail: the node itself (vue-router exact-active styling on its link) and its parent, which received the deepest-visible-ancestor highlight. The ancestor rule now returns nothing when the active folder is the single hidden child the "..." stands for, so exactly one row reads as selected. Navigation through the node is unchanged, and the multi-hidden-children case keeps highlighting the parent row as before. * feat(vault): show a loading state while switching folders Navigating to another folder used to keep the previous folder's rows on screen until the new ones landed, then swap them mid-air - CnIndexPage only shows its full loading spinner while it is loading AND has nothing to render. A folderSwitching flag now blanks the list for the duration of the navigation's fetch, so a switch reads as spinner -> new folder's contents. The flag starts true because root <-> folder navigations REMOUNT the view (the routes are different manifest pages and CnPageRenderer keys its render on the page id), and a fresh mount rendered the store's previous rows before mounted() fetched anything - the folderId watcher only covers folder -> folder moves. In-folder refreshes and searches keep the in-place behavior. * l10n: translate the English-copied strings across all locales A sweep over every locale for values identical to their English source found six bulk-copied strings: "Applications awaiting approval", "No applications awaiting approval" (31 locales), "Your secrets, folders and applications at a glance", "Vault overview", "Rotation due" (33 each) and "View all" (31). All now carry real translations phrased with each locale's established terminology. Script-mismatched loanwords are also fixed: Token and Status become Cyrillic in mk/sr, and Link becomes Länk/Ligação/Colliaziun in sv/pt/rm. Values that already held a genuine translation were never touched, and legitimate loanwords (URL, Token in Latin-script locales, Dashboard, Widgets, Layout) and the brand string "Keepiq {version}" stay as they are. The .js catalogs are regenerated. * fix(l10n): translate the secret-type labels everywhere they render The secret types come from the server with English labels seeded by SeedSecretTypes.php, and the frontend printed type.label raw - so the type filter, the create and edit dialogs' type selectors and the detail sidebar's type subline showed English in every language. A new secretTypeLabel() helper (src/utils/secretTypes.js) routes the label through the translator at all four sites, falling through unchanged for custom types without a catalog entry - the dynamic-key pattern the nav already uses for manifest menu labels. The ten system labels are translated in English plus all 36 required locales (295 new entries; seven keys were missing from every catalog), the .js catalogs are regenerated and the parity ratchet is tightened accordingly. * fix(security): remove the dev auto-unlock block that slipped into the l10n commit Commit 17988a7 accidentally carried a local development aid into LockScreen.vue: a DEV-ONLY auto-unlock block that reads the master password in plaintext from localStorage and unlocks the vault on page load, plus its status banner and data field. It was guarded by a NODE_ENV check (webpack strips it from production builds) and inert without an explicit localStorage opt-in, but it defeats the lock screen's security model and was never meant to be tracked. The same commit also dropped two @SPEC tags from handlePasskeyUnlock; they are restored. The file now matches its pre-17988a77 state exactly. * feat(vault): fleet selection with a contextual strip, vault strip, top-row toolbar The vault list adopts CnIndexPage's own selection: selectable with rowClickToView (clicking opens the detail, checkboxes select), selectedIds fed from the bulk store and @select synced back. The table gets header select-all, row checkboxes and selected-row highlight; cards get a checkbox each; the list keeps shift-range selection through NcCheckboxRadioSwitch — the same Nextcloud checkbox the other views render — with a capture-phase shift recorder. One selection is shared across view switches, pruned to the visible rows on every list change (folder navigation, page flips, filters, refresh), and the strip's bulk buttons disable while the list is (re)loading so a stale selection from the previous page cannot be moved or deleted. Bulk actions live in the library's contextual selection strip alone (live count announced via role="status" — WCAG 2.1 SC 4.1.3 — plus Move, Share, Add to team folder and Delete with icons, and the strip's own Clear; bulk-actions §3.1's required bar). The Actions menu keeps only the Select-all checkbox — the sole whole-view select-all for the list and card views. Selected list rows tint the item with its hover token, never the checkbox gutter. Folders left the collection: vaults/subfolders render as a captioned strip (Vaults at root / Folders inside) above the collection in every view mode (the library's #before-collection slot), so they no longer masquerade as secrets in table/cards and survive pagination; the strip owns the single divider. Breadcrumbs render below the actions bar, above that strip, a step larger — the trail is the folder heading now that the page title is visually hidden (the h1 stays for accessibility). The bar owns the top row: keepiq's doubled page padding is dropped and the bar clears the floating nav toggle with a 30px !important margin — a margin so its background starts after the toggle, !important because design-system themes flatten the bar's box with their own !important rules. Keepiq-scoped, as is the container-scale rounding; other apps keep their heading and the library defaults. The secret-type filter is a funnel button beside the search field carrying the sort radios too; it flips to its filled glyph in the primary color whenever a type filter is set or the sort deviates from the default name sort. New l10n string "Filter and sort" in en + all 36 required locales. e2e: clickOverflowAction opens the bar menu via a new openActionsMenu helper; export-gdpr's four inline menu openers use it. * fix(nav): the "…" node carries the selection for any folder below the display cap Being in a folder deeper than the rail renders (e.g. test11 under the 5-level cap) highlighted the deepest visible ancestor (test5), which read as the wrong folder being open. The active trail is computed once (activeFolderTrail); when it exceeds the cap no ancestor row lights up and the "…" node under the deepest visible branch renders active instead (ellipsisHighlightId, passed into NavFolderTree's ellipsis item) — exactly one row selected, and it is the row that stands for the hidden chain. This subsumes the earlier single-hidden-child special case. * fix(icons): draw the lock shackle as a filled outline in every app icon ImageMagick's internal SVG renderer — used by Nextcloud theming to generate the favicon — does not draw stroked paths, so the shackle vanished from generated favicons. All five icons (app, app-dark, app-store, pwa-icon, pwa-icon-maskable) redraw the shackle as a filled outline of equivalent thickness, so stroke-less rasterizers render the full lock. * style(vault): no blank line after the crumbs custom property CI's stylelint (declaration-empty-line-before) rejects a blank line between a custom property and the following declaration — a formatting habit that is valid in nextcloud-vue's stylelint config but not in keepiq's. The full stylelint sweep is green again, and RESTYLE-PLAN's per-stage verification list now names stylelint so the gate stops being skippable locally. * fix(quality): satisfy the four failing hydra gates gate-16: @SPEC tags on the seven changed methods the gate named — onRowCheck/onSelectAll (bulk-actions multi-select requirement), the selectedFolderId watcher (folder management), typeFilterOptions and both secret dialogs' typeOptions (secret types), and the detail sidebar's mounted() (read secret). gate-40: the list checkbox's ariaLabel binding becomes aria-label — identical Vue behaviour, and the form-label rule recognises the kebab form. gate-45: the sidebar's accordion-chevron transition gains the prefers-reduced-motion fallback the rest of the file already has. gate-46: five @SPEC tags pointed at anchors that never existed — requirement-secret-sharing is requirement-share-a-secret (user-sharing) and requirement-cached-vault-is-read-only is requirement-offline-mode-is-strictly-read-only (offline cache). * chore(deps): bump @conduction/nextcloud-vue to 2.27.0 Brings the released library features the vault page consumes, until now only available through a locally built dist overlaid into node_modules: the contextual selection strip (live role="status" count, #selection-actions slot, clear-selection wiring), the #after-search and #before-collection CnIndexPage slots, the role-grouped actions-bar layout with its narrow-width container-query tiers, clickToView on CnCardGrid/CnObjectCard, and the CnPageRenderer fix that stops lifted manifest fields (title, icon) falling through as HTML attributes on custom pages — the "Vault" tooltip that hovered over every custom page. With the package live, the overlay workflow is retired for this line of work: node_modules now matches the lockfile again. * style(sidebar): overflow-wrap instead of the deprecated word-break keyword CI's stylelint (declaration-property-value-keyword-no-deprecated, in the newer toolchain the merged lockfile installs) rejects `word-break: break-word` — a deprecated keyword that always aliased overflow-wrap behaviour. The detail rows' value wrapping now uses `overflow-wrap: anywhere`, which wraps long secret values identically.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 13:51 UTC
Download the full PDF report from the workflow artifacts.
…#571) The folder-sharing spec drove the new sidebar's "Secret actions" menu via getByTestId('secret-detail-move'), but data-testid falls through to NcActionButton's ROOT element, which is the <li> wrapper rather than the <button> that carries the click handler. Dispatching a native click on the <li> is a silent no-op, so SecretMoveDialog never opened and the spec failed on the downstream '.move-form' assertion instead of at the click. The testid resolving fine is what made this read as a dialog bug: the click step passed and only the next assertion timed out. Descend to the button so the handler actually fires. The sibling testids in this spec (secret-detail-share, secret-detail-more-info) sit on NcButton and a native <summary>, whose roots ARE the interactive element, so they are unaffected.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Four review conversations on the merged PR #479, all in SecretDetailSidebar: - isOwner reads the single canonical `ownerId` field; the owner_id/userId fallback chain was dead code (the Secret entity has serialized ownerId since its first version) and the pinning test now asserts fail-closed behaviour for legacy-style payloads instead. - mounted() catches a failing fetchTypes() and surfaces the error instead of stranding the sidebar on an infinite spinner. - refreshList() no longer swallows a failed list refresh silently; it shows an error toast so a stale list is signalled. - remove() catches a refused delete (403/500/offline) and shows the reason inline instead of leaving the sidebar open without feedback.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-31 14:14 UTC
Download the full PDF report from the workflow artifacts.
The secret detail sidebar swaps the route's :id segment without remounting, so AttachmentPanel's mounted()-only fetch kept showing the first opened secret's attachments on every secret opened after it. Add a secretId watcher that resets the attachment store (no stale rows while loading, none left behind on a failed fetch) and refetches, mirroring the sidebar's own route-driven reload. Covered by a new component regression test.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 06:11 UTC
Download the full PDF report from the workflow artifacts.
The manifest's first-visit tour was translated only in Dutch; the other 36 catalogues missed 12 of its 13 strings and the three menu labels it points at (Features & roadmap, My activity, Flows). Add translations to every catalogue, embedding each language's own menu-label wording so the tour text matches the rendered nav — the Dutch tour is fixed on the same point (it quoted the English labels). Correct wrong-language entries for the referenced labels (Czech/Slovak/Slovenian/Macedonian carried Croatian; Belarusian/Ukrainian carried Russian; Catalan Spanish; Swedish Danish; Luxembourgish German; Romansh Italian), add entries for the two strings introduced by the PR #479 sidebar fixes, and regenerate the .js catalogues (npm run l10n:build).
Fourth app off `#` routing, after stackiq (softwarecatalog#899), zaakafhandelapp (#609) and opencatalogi (#1341). Two parts: 1. createWebHashHistory -> createWebHistory. 2. routerBase(), derived from the URL actually being served. Nextcloud serves the app under BOTH /apps/keepiq/... and /index.php/apps/keepiq/..., but generateUrl() returns only the form the instance is configured for. Arriving on the other leaves the path outside the router base, vue-router cannot resolve it, and the catch-all redirects to '/' -- no error, deep link silently swallowed. This app has the sharpest version of that problem: keepiq's share and send links (/share/link/:token, /send/:token, /share/request/:token) are handed to people OUTSIDE the app, in messages and emails, in whichever URL shape the sender's client produced. A swallowed deep link there is a share that silently does nothing. 6 spec files built URLs with a hash; all now use real paths. Verified against the published @conduction/nextcloud-vue (USE_LOCAL_LIB=false). The vault is locked in this environment, so every route redirects to the lock screen -- which is itself the proof that the router parsed the request first: /apps/keepiq/secrets -> /apps/keepiq/lock?returnUrl=/secrets /index.php/apps/keepiq/secrets -> /index.php/apps/keepiq/lock?returnUrl=/secrets RELOAD on the /index.php form -> 200, still on the lock path No hash anywhere, both URL forms work, and the requested route survives into returnUrl. (/folders answers returnUrl=/ because the real route is /folders/:folderId, so the catch-all correctly claims the bare path.) eslint exits 0 and prettier --check on the FULL glob is clean.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 08:55 UTC
Download the full PDF report from the workflow artifacts.
Follow-up to #582, which turned E2E red on development. Three selectors and one URL assertion still described hash routing: a[href$="#/"] the Dashboard nav entry a[href$="#/lock"] the Lock vault entry (twice) toHaveURL(/#\/lock$/) and /#\/(?!lock)/ after unlock Under history routing those match nothing, so the nav assertions failed on 'element(s) not found' rather than on anything about navigation. Matched as SUFFIXES rather than whole hrefs: Nextcloud serves the app as both /apps/keepiq/... and /index.php/apps/keepiq/..., and the link carries whichever base the page was loaded under, so pinning either would fail on the other for a reason that is not a defect. The existing comment already made that argument for the hash form; it now makes it for the path form. The post-unlock assertion is inverted rather than retargeted: it now asserts we LEFT /lock instead of naming the destination, which varies with the returnUrl the gate captured. Verified: no hash href selectors and no hash URL assertions remain in tests/; prettier --check on the full glob is clean. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Restyle Stage 9 backend: nullable custom_icon + custom_color KEY columns (lowercase kebab, max 64 chars — never hex or free text; the frontend owns the catalogs, and an unknown key renders as the fallback glyph there, which keeps old servers forward-compatible with newer frontends) on doriath_folders, serialized on the Folder entity so the offline vault cache carries them for free. FolderService gains updateAttributes(id, changes, userId): owner-checked, key-present-with-null CLEARS, absent key untouched — so a rename/move request that does not mention the attributes can never wipe a color (the 53a3600 semantics). create() forwards the two optional keys through FolderTreeService; both paths validate the key format. The controller applies attribute changes only for keys PRESENT in the raw request body (array_key_exists on getParams()), the distinction the typed arguments cannot make. Covered by seven new FolderServiceTest cases: set / clear / untouched, ownership rejection, format rejection on both paths, create-with-keys.
…ult dialogs Restyle Stage 9 dialogs, over the library's new CnIconColorPicker (color swatches + searchable icon grid + themed preview; the Default cells emit explicit null, which is the whole reset story): - FolderCreateDialog: the picker at VAULT level, and the create flows are now FIXED by the opening context (team decision) — a vault is only ever created at the root, so the New-vault dialog has no parent picker at all, and a folder only ever inside a vault, so the folder flow's parent picker no longer offers the root. The two flows can no longer morph into each other mid-dialog. - NEW FolderEditDialog: rename + (vault-level) the picker, seeded from the folder. This REINTRODUCES rename — the store's updateFolder had no UI caller since the old FolderTree left. For a vault the two customization keys are always sent, so a Default pick genuinely clears server-side (key-present-with-null). - NEW FolderMoveDialog: vaults themselves never re-parent (team decision) — "Move vault contents" transfers everything INSIDE the vault into another vault (direct secrets via updateSecret folderId- only, direct subfolders with their subtrees via updateFolder). The target picker offers only the OTHER vaults, each rendered with its own icon and color; an empty vault says there is nothing to move; a mid-way failure is reported honestly (the transfer is item-by-item, no bulk endpoint exists). - Store: createFolder forwards the two keys; updateFolder documents the null-clears contract. The vitest library stub grows the picker and the catalog resolvers. Covered by new FolderCreateDialog and FolderEditDialog component specs (vault/folder flow split, payload shapes, explicit-null reset, inline error).
… a vault actions menu Restyle Stage 9 rendering + the vault "..." menu: - NavFolderTree depth-0 entries render the vault's picked icon in its theme-variant color on the Proton-style tinted circle — the tint is derived from the SAME resolved hex (folderColorTint, the 53a3600 approach), so glyph and circle can never disagree across a live theme flip (currentTheme() is reactive). On the ACTIVE row the circle goes OPAQUE in the theme's main background with the colored glyph on it: exactly the rest-state foreground/background pairing at unchanged contrast, so the color identity survives the selection without the glyph ever sitting on the saturated primary highlight. Every fill falls back to the string 'currentColor' — an explicit null fill-color strips the SVG fill attribute and renders black regardless of theme. - Each vault entry carries an actions menu (vertical-dots trigger via the menu-icon slot; the trigger goes transparent on the active row instead of reading as a stray light pill): Edit vault / Share vault / Move vault contents / Delete vault. KeepiqAppNav hosts the dialogs — share reuses TeamFolderDialog (the same team-sharing flow as the list toolbar), delete finally WIRES UP the orphaned SubfolderResolutionDialog (per-subfolder keep/move/delete protocol) with a plain confirm for empty vaults, and navigates back to the vault root when the open folder was the deleted one. - The list's vault rows (SecretList #before-collection strip) render the same glyph treatment; subfolderRows() passes the two keys through as explicit nulls when unset. Covered by the extended vault-list spec (pseudo-row pass-through).
The Stage-9 catalog and dialog strings in en + all 36 required locales, with the .js browser catalogs regenerated (npm run l10n:build): - the picker labels routed through CnIconColorPicker's translate prop (Color, Icon, Search icons, Default), the 12 color names and the 42 icon names (each language reuses its existing vault/menu terminology); - the vault actions menu and dialogs: Edit/Share/Delete vault, Move vault contents, the contents-move explainer, empty-vault lines, the Target vault label and the honest partial-failure error, plus the empty-delete confirmations; - the two PR #479 sidebar strings that were still missing from the earlier catalog commit (Could not refresh the list, Failed to delete secret). The earlier "Move vault"/"Move folder"/re-parent hint strings are retired but kept in the catalogs, matching how other superseded strings are handled. Extraction and parity gates green.
* chore(lint): let the linter see tests/ and scripts/
`npm run lint` was `eslint src`, so two whole trees were never linted. Across
the fleet that hid roughly 3,900 errors, none of which any CI leg had shown.
Most of it was the config, not the code, and the same two defects were in
every app.
**scripts/ had no config block at all.** These are CommonJS Node CLI checkers,
and flat config defaults every `.js` to ESM with browser-ish globals, so eslint
read the CommonJS wrapper itself as undefined identifiers: `require`,
`process`, `__dirname`, `__filename`, `module`. A `scripts/**` block now
declares the environment. Declaring beats suppressing here: `no-undef` is the
rule that catches a genuinely misspelled identifier, and dozens of fake
findings would bury a real one. A second block covers `scripts/**/*.mjs`, which
is ESM and needs Node's globals without the CommonJS wrapper.
**The tests block applied a non-TypeScript-aware rule to TypeScript.** It named
`tests/**/*.ts` while setting the CORE `no-unused-vars`, which v9 deliberately
turns off for `.ts` in favour of the `@typescript-eslint` version. The core
rule reads the parameter names inside a function TYPE as bindings, so
t?: (app: string, key: string) => string
reports `app` and `key` as unused variables, and every unused `catch (e)` in a
`.ts` spec reports twice. The block is split now: `.js`/`.mjs` on the core
rule, `.ts`/`.tsx` on the TypeScript one, same patterns on both.
Also: stale `eslint-disable` comments naming plugins eslint 10 no longer
registers, which are themselves errors ("Definition for rule ... was not
found"), and a rule that must not parse shell scripts.
The genuinely real findings were the useful part: dead locals, unused imports,
dead helper functions, unused `catch` bindings, extensionless relative imports,
and a handful of `== null` comparisons spelled out so they still match null AND
undefined.
Verified per app: `npm run lint` 0 errors over src + tests + scripts,
`prettier --check` clean, and the unit suite still green.
* fix(lint): convert the last two import() type annotations
CI caught what my local check had missed: I measured with `eslint tests
scripts` while CI runs `npm run lint`, which is `eslint src tests scripts`.
The narrower instrument reported this app clean.
Four of the six were import-ordering and `--fix` handled them; these two are
`page: import('@playwright/test').Page` annotations, now a real `import type`.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-01 10:35 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 11:00 UTC
Download the full PDF report from the workflow artifacts.
The keep-the-color-when-selected attempt (an opaque main-background disc under the colored glyph) is rejected on review: the disc read as a stray pill on the row highlight. On the highlighted row the vault glyph now follows the row contrast color — white on the highlight, exactly like the collapse chevron — with no tint circle behind it. Color identity shows at rest and on hover. The flattening is enforced in CSS, keyed to the row's own `.active` class rather than only the highlightId prop: the class has MORE sources than the prop (NcAppNavigationItem also activates through vue-router's own link matching), and a colored vault carries its hex as the svg fill ATTRIBUTE, which only a fill PROPERTY overrides. Two companion rules fix inheritance gaps NC's legacy-active styling leaves open: the icon column gets the primary-contrast token explicitly (the active rule whitens only the LINK element, so inherited currentColor resolved to main-text black on themed rows), and the "..." actions trigger gets the same token (its earlier `color: inherit` fell into the same trap — the collapse chevron is only white because NC hands it the tertiary-on-primary variant, which the menu toggle never gets). Covered by a new NavFolderTree glyph spec (highlighted = currentColor fill + no tint style; rest = color + same-hex tint; collapsible and leaf vaults identical).
The lint pass left two shapes that eslint and prettier both call clean while
the Playwright suite fails to load. `development` collected ZERO tests.
A Playwright callback's destructuring is a FIXTURE REQUEST, not a binding list.
Renaming an unused `request` to `_request` asks for a fixture that does not
exist and the file never loads. The binding is renamed, the key kept:
async ({ page, request: _request }) => {
And the type-import conversion added a second `import type { Page }` to a file
that already imported `Page` alongside `ConsoleMessage`, which is a
duplicate-identifier SyntaxError.
`npx playwright test --list` parses every spec without running one, and is the
check that catches both. A green linter cannot.
Settled after trying both alternatives live (a plain white glyph, and a translucent tint that sank into the highlight): the selected row shows the vault's COLORED glyph on a disc in the theme's main background — a white disc in light mode, a dark disc in dark mode. That recreates exactly the rest-state foreground/background pairing (light palette variants on a light surface, dark variants on a dark one), so the color identity survives selection at unchanged contrast. Colorless vaults keep no circle and follow the row's text color, and the icon-column CSS rule (which also keeps the chevron side and nested folder glyphs legible on the highlight) stays — it cannot touch colored glyphs, whose hex rides the svg fill attribute. The glyph spec is re-pinned to this contract and records the decision so it is not relitigated.
The release that carries nextcloud-vue#906 — CnIconColorPicker, the folder-customization catalogs and resolvers, folderColorTint and useCurrentTheme — so keepiq builds from a clean install again and the Stage-9 local-dist overlay retires (the Stage-4 CnBreadcrumbs pattern, now closed out the same way). Verified against the published package: all picker polish rounds are in (the release build extracts component CSS to dist/esm/nextcloud-vue.css rather than inlining it), the PR review added keyboard a11y and spacing tokens on top, and the library now imports dexie as a BARE dependency instead of snapshotting a copy into its dist — one Dexie per page by construction, resolved from the app (4.4.5), which retires the two-versions crash class for good.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 12:57 UTC
Download the full PDF report from the workflow artifacts.
The path-routing migration (e225828) left the workflow suite navigating by `location.hash`. Under `createWebHistory` that still changes the URL and still fires `hashchange`, but the router does not listen to it, so the route never changed and nothing threw. Every caller failed much later on a missing `.secret-list-item`, which read as a broken vault rather than a navigation that did nothing at all. `gotoVaultRoute` now pushes through the router instance, with a pushState + popstate fallback that drives `createWebHistory`'s own listener. Both keep the navigation in place, which the vault requires: the CryptoKey lives only in memory, so any reload drops it and the guard bounces to the lock gate. `openVault` delegates to the same helper instead of carrying a second, also hash-shaped copy of the logic, and the two specs that set `location.hash` inline now call the helper.
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 13:59 UTC
Download the full PDF report from the workflow artifacts.
Six phpcs errors and one phpmd violation, all introduced by the vault customization work and all blocking PR #591: - FolderController's update() docblock started its long description in lower case, and the migration used an inline `/** @var */` block where the standard wants `/* */`. - Four assertAttributeFormat() calls passed positional arguments; the standard requires named parameters for internal calls. - FolderController::update() reached a cyclomatic complexity of 12 (the threshold is 10 and it trips AT the threshold, not above it) once the attribute branches joined the rename/move ones. Two private helpers now carry them: attributeChanges() collects the key-present-with-null changes from the raw request body, and applyUpdates() runs the rename/move/attributes sequence and resolves the folder when nothing mutated. update() is left with the auth check and the error mapping, which is what it should have been reading as all along.
…alog Hydra gate-13 (modal-isolation) failed on PR #591: the empty-vault delete confirmation was an inline NcDialog inside KeepiqAppNav, and the gate requires modals to live in src/dialogs/ or src/modals/. It is now FolderDeleteConfirmDialog, which owns its own delete call, busy state and inline error and emits `deleted` — the same contract SubfolderResolutionDialog already offers, so the rail hosts both the same way and keeps only the choice between them. Also adds the @SPEC anchors gate-16 (spec-coverage) wants on the rail methods this branch introduced — onDeleted, onMoved and leaveDeletedRoute trace to folder-management, resetDelete is tagged exclude as dialog-host state plumbing.
… frontend The two remaining CI failures on PR #591: - Frontend Check (format): nine files from this branch were never run through prettier. Note for anyone reproducing this on Windows — with core.autocrlf=true every file fails `npm run format`, because the working copy is CRLF and prettier expects LF; run it with `--end-of-line auto` to see the real offenders. - Hydra gate-16 (spec-coverage): the dialogs this branch adds are new files, so every method in them is in the diff and needs an @SPEC anchor. The domain methods trace to folder-management; the dialog open-state plumbing and the form-enablement guards are tagged exclude with the reason at the point of use, which is what the gate's waiver mechanism is for.
Gate-16 (spec-coverage) reports the watcher this branch adds to AttachmentPanel as a changed method with no @SPEC anchor. The watcher re-runs the panel's existing fetch when the hosting sidebar swaps the :id route segment. It states no attachment behaviour of its own — the six requirements in openspec/specs/encrypted-attachments cover upload, envelope shape, quota, export, deletion cascade and audit, none of them a refetch-on-route-swap — so it carries a reason-bearing @SPEC exclude rather than a false anchor to the upload requirement. The file's header anchors continue to state the behaviour itself.
SuppressionHygieneTest requires every @SuppressWarnings under lib/ to carry its reason on the tag's own line, at least 12 characters after the closing paren. The bare tag added to satisfy phpmd in fc2a9ee met phpmd and failed that test, taking the whole PHPUnit matrix with it. $output and $options are unused because SimpleMigrationStep::changeSchema() mandates them; the reason now says so, in the same shape the sibling migrations and background jobs use.
…another secret's files The secret-detail sidebar swaps its :id without remounting, so AttachmentPanel refetches in place. Nothing ordered those responses: a slow list for the previous secret could land after the current one and repopulate the panel with a different secret's attachment metadata. fetchAttachments now takes a monotonic listRequestId and writes attachments, error and loading only while that id is still current, and reset() bumps the id so an in-flight request is retired rather than allowed to repopulate after the reset. reset() also clears loading, which it never did — a reset during a fetch left the spinner running with nothing left to finish it. The guard sits in the store because that is where the write happens; a component-level key can only decide whether to start a fetch.
feat(vault): Proton-style vault icons and colors, vault actions, and post-#479 fixes
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 09:53 UTC
Download the full PDF report from the workflow artifacts.
… inside (#602) 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>
Quality Report — ConductionNL/keepiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 111/111 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 19:19 UTC
Download the full PDF report from the workflow artifacts.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.