Release: merge development into beta - #563
Open
github-actions[bot] wants to merge 112 commits into
Open
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.
Contributor
Author
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.
Contributor
Author
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.
Contributor
Author
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.
Contributor
Author
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.
Contributor
Author
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-03 12:01 UTC
Download the full PDF report from the workflow artifacts.
Every push to development fired Code Quality TWICE on the same commit: once for the push event, once for the pull_request event of the permanently-open "Release: merge development into beta", whose head_ref IS development. The concurrency block is not the bug and must not be changed: it suffixes the group by event name deliberately so the push run is NOT cancelled by the PR run, because the push run is the only carrier of Coverage Baseline Check, SBOM and Features Extract. The consequence is that both lanes run to completion, so the duplicate has to be dropped at the job. The PR run is the one to drop: its head sha IS development's, which the push run already decided, and it runs strictly fewer jobs. Not done by removing `beta` from pull_request.branches, though that would also stop it: the release/v* and sync/main-to-beta PRs target beta too and genuinely need their run. Only the head_ref:development PR is redundant. A development -> main promotion PR is skipped by the same clause, for the same reason and just as correctly. Proven on openregister#3370 before rolling out: run 33757014196 (pull_request, sha 4f3ef5e9) completed as `skipped` with ZERO jobs in 72s, while the push run for the same sha ran the full suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SPA is moving its recipient links from hash URLs to paths under the
anonymous shell (/apps/keepiq/public/share/link/<token> and friends), so
a load or refresh of any such path must serve the shell without a
Nextcloud session. A publicShell catch-all route (/public/{path},
mirroring the AppHost dashboard#page / dashboard#catchAll split - a
distinct name, because Symfony silently replaces same-named routes)
serves the same #[PublicPage] template at every subpath. It sits in
$extra, so it precedes the authenticated /{path} fallback.
…links The router runs createWebHistory, which never reads a URL fragment - yet the recipient links in the wild are hash URLs (/apps/keepiq/public#/share/link/<token>): vue-router saw the bare /public path, the catch-all redirected to /, and the vault guard bounced account-less recipients onto the lock screen, asking for a master password they do not have. Two changes, one per link population: - routerBase() extends to /apps/keepiq/public on the anonymous shell, so the SAME manifest routes (/share/link/:token, /send/:token, /share/request/:token) resolve on both shells unchanged. - Links already sent cannot be regenerated, so a bootstrap handoff (src/bootstrap/hash-route-handoff.js) rewrites any legacy #/ URL in place with replaceState before the router is created. A legacy fragment-mode send key (?k= INSIDE the old SPA fragment) moves to a new #k= fragment, never into the real query string, which the browser would send to the server on the next refresh. isPublicSurface() now classifies by path prefix, keeping the hash prefixes for reads that can happen before the handoff.
The builders of recipient-facing links still emitted the retired hash form, which the createWebHistory router never reads - recipients got the lock screen. All four now emit paths under the anonymous shell: - LinkShareController (share links) - ApplicationSecretRequestsController (application fill links, a fifth site the fix brief's list missed) - fillLink.js (secret-request fill links) - ephemeralSend.js (sends) The ephemeral-send content key rides as a #k= fragment - still never transmitted - and the access page reads it from the fragment with a fallback to the legacy route query, so links from before this change keep decrypting. The send page's card styling rides along with its fragment-key reader. The stale hash-routing wording in the manifest note is corrected.
… PWA manifest Sweeping for the retired hash scheme turned up three more builders outside the fix brief's list, all on the AUTHENTICATED shell: the unified-search provider, the notification deep link, and the PWA manifest's vault shortcut. Their /apps/keepiq/#/secrets/<id> links fell through the catch-all to the dashboard - the deep link silently swallowed. They now emit paths; the gated route lands on the lock screen with the path as returnUrl and resumes there after unlock.
No spec ever opened a share, send, or fill link without a session - the existing page-surface specs drive the same components on the authenticated shell with the admin cookie jar, which is precisely why dead recipient links could ship. This spec mints fixtures through the authenticated API (the server stores only opaque blobs, so no real crypto is needed) and opens the links in a browser context with an EMPTY storage state: the path form, the RETIRED hash form (through the bootstrap handoff), a legacy fragment-mode send - asserting the content key stays out of the query string - and a fill link.
…d-password message Every string an account-less recipient sees on the share access and ephemeral-send pages now has a Dutch translation (18 keys; the fill page was already covered). The nl parity ratchet is lowered 382 -> 362 so the paid-off debt cannot silently return. Also adds "Invalid password. Please try again.", translated into all 36 locales, for the link-share access page to adopt: that page compares nothing against a second entry, so the existing "The password does not match" wording - correct on the lock screen, where it compares the two typed setup passwords - is wrong there.
…ecret as copyable fields The recipient card sat pinned in the top-left corner: core's guest layout makes #content.app-public a flex row, so #keepiq-app shrank to the card's width and the card's `margin: auto` had no free space to distribute. The app mount now fills the flex row (app.css) and the public shell centres its card on both axes with flex + margin:auto, which stays scrollable when the card outgrows the viewport. All three recipient views share the same card treatment. The revealed link share was a bare <dl> that printed a card secret as its raw JSON composite. It now renders labelled field rows with a copy button per row (CopyButton, clipboard auto-clear) and masked sensitive values behind an eye toggle - card/identity composites are parsed via parsePayload() into Card number / Expiry / CVV / PIN / Cardholder rows (number, CVV, PIN and BSN masked), additional fields render as masked rows, and a plain key stays one masked "Secret value" row. The usage-cap notice moved to the top of the card, under the heading, where a whole-card status banner belongs. The terminal not-found/expired state is a recognisable error page (broken-link icon, "Link not found or expired" heading, translated explanation) instead of the raw English server message, and a failed decrypt now says "Invalid password. Please try again." The password form moved to house-style controls.
The freshly created link and password - which cannot be retrieved again - render in a highlighted panel so they read as the thing to take away from the dialog. Sections gained real padding and separators, rows breathe, the password is proper monospace at a readable size, and the disabled user-sharing affordance is visually set apart from the working controls. No copy changes; the folder-sharing e2e selectors are unchanged.
#611) * feat(nav): give keepiq a Reports page, from the reports it already had ADR-114 Decision 3 puts Reports in every app. Keepiq needed nothing invented: it already had TWO reports and they were two footer menu entries. PasswordHealth component HealthReportView, and its own note calls it a "health report" PersonalActivity the session user's own audit-trail feed My first fleet audit called keepiq an app with no reports. That was a keyword regex over page ids and titles, and neither "Password health" nor "My activity" contains a report word — the COMPONENT name does. The query decided the answer. WHAT MOVES. A type:"reports" page cards both, and the two menu entries are DELETED. ADR-112 Decision 2: a report is a card or an entry, never both. Deleted rather than listed in menu-layout's `removals`: that key retires an entry that still EXISTS, and gate-53 reports a removal naming an absent entry as a stale removal. I had it the wrong way round first and the gate said so. The PAGES stay routable under the same route names, which is what keeps deep links, the dashboard widget's viewAllRoute and the older specs working (ADR-044 Decision 5). An e2e test asserts that rather than trusting it.⚠️ The walkthrough's `go-personalactivity` stop targeted the retired nav entry. An OPTIONAL step whose target is absent is SKIPPED SILENTLY — no error, no change to the step count — so it now points at Reports. Footer renumbered to the canonical 90/95/100 from 80/85/86/87. ChartBoxOutline registered in src/icons.js: an unregistered name renders no glyph at all. NO STORE, and that is deliberate. Keepiq's OpenRegister register declares zero schemas — secrets live in its own tables — so `installable` could name nothing and an install would have nothing to write. ADR-080 Decision 4 refuses the word Store on a surface that cannot honour it. The e2e asserts the absence, so the day keepiq grows one, something notices. Six new strings, translated into all 36 locales the parity ratchet requires; "Reports" harvested from decidiq's catalogues for the 24 locales that had it. Verified: gate-22, gate-53, gate-60, gate-70, gate-102, gate-104 and gate-107 all clean, the parity ratchet reports no regression, and all five new e2e tests collect. eslint could not run here (keepiq's node_modules is not installed); CI lints it. * fix(router): gate the Reports route, and format the chrome spec Two CI failures on this branch, both correct catches. ROUTER GUARD. tests/router/guards.spec.js pins every page id in src/manifest.json against an explicit PROTECTED/PUBLIC classification, so a page added to the manifest without being classified fails the drift check instead of silently inheriting whichever default the guard applies. Adding Reports made it 17 routes against 16 classified. That test is doing exactly what its own comment says it exists for. Reports is classified PROTECTED, not public. It is the page PasswordHealth and PersonalActivity now live on as cards, and both read vault state: Password health analyses the UNLOCKED vault in memory, and My activity is the caller's own audit trail. A Reports page reachable while the vault is locked would be a way around the lock even if each card then refused. PRETTIER. tests/e2e/app-chrome.spec.ts was not formatted. Run through prettier; `prettier --check` over the whole tree is clean. The guard suite passes 71/71. The full vitest run reports 647 tests passed and zero test failures; 16 test FILES failed to collect, which is this sandbox rather than the branch — I had symlinked another app's node_modules to get prettier and vitest to run at all, and removed it after. CI installs the app's own dependencies, so read those 16 there rather than from me. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Found by RUNNING the spec against a throwaway Nextcloud rather than collecting it. All 5 keepiq specs now pass; before this, all 5 failed. Keepiq redirects every route to /lock until a vault exists, and the app navigation is present in the DOM but NOT visible behind the lock screen — so the failure reads "cn-nav hidden" and names nothing useful. The suite's global-setup logs in and suppresses the walkthrough but does not create a vault; its own comment says "every spec here unlocks", and this is the first spec that needs the app UI.⚠️ A FULL PAGE LOAD RE-LOCKS THE VAULT, so unlocking once in beforeEach is not enough: every page.goto lands on /lock again. Navigation now goes through a gotoApp() helper that unlocks after each load, which is the only reason the routability test passes. Also carries the fleet-wide chrome-spec fixes: the cn-nav-* testids are on the <li> wrapper rather than the clickable <a>, and the first-run setup wizard modal intercepts pointer events on a clean install.
fix(e2e): make the chrome specs pass in a real browser
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.9 to 2.2.10. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [web-token/jwt-library](https://github.com/web-token/jwt-library) from 4.1.7 to 4.2.1. - [Commits](web-token/jwt-library@4.1.7...4.2.1) --- updated-dependencies: - dependency-name: web-token/jwt-library dependency-version: 4.2.1 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>
…application-dialogs
…iles-relock-vault
…type-icon-strength-badge
…ic-share-link-routing
…ialogs fix(applications): render the register and private-key dialogs as real overlays
…vault fix(dashboard): stop the quick-action tiles from re-locking the vault
…trength-badge fix(secrets): complete the cards and table views and move vault context to Proton-style indicators
Four KeepiqNotifierTest tests failed across all six CI combos, and the URLs were not why: commit 33bf6c7 already moved every expectation to the path form. That same commit round-tripped the file through a CP1252 reader, turning all eight em dashes into "a-euro-quote" (c3a2 e282ac e2809d where e2 8094 belongs). Four of the eight sit inside asserted message strings, so exactly four tests failed - the other four occurrences are in comments and were silent. Restores U+2014 in all eight places. Nothing else about the file changes; development still carries the correct bytes, so the diff against it is now empty for these lines.
Hydra gate-16 failed on src/views/EphemeralSendAccess.vue::fragmentKey - the computed was added with an inline comment but no docblock, so the changed-method scan found no @SPEC line. Adds the docblock with the anonymous-recipient anchor the neighbouring mounted() hook already uses.
isPublicSurface() classified any path merely CONTAINING '/apps/keepiq/public' as a recipient surface, so /apps/keepiq/publications/..., /apps/keepiq/publicfoo and /apps/keepiq/secrets/public all answered true. It is not the security gate - access is authorised server-side by scoped token, and the locked-vault gate is the name-based isPublicRoute() - but a classifier that is wrong for a whole family of paths invites being mistaken for one, and its docblock read as if it were authoritative. Splits the app-relative remainder off the pathname first, then compares prefixes against that. The app segment is still LOCATED rather than anchored, so sub-directory installs (/nextcloud/index.php/apps/keepiq/..) keep resolving; what it now requires is that the remainder begin at a "/" boundary, which also rules out another app whose id merely starts with this one. The docblock says plainly what the function is not. Specs cover the former false positives, the neighbouring-app id, and the webroot install the boundary check must not cost.
publicShell#pageCatchAll ('path' => '.+') matches every GET below
/public/ and hands it to a #[PublicPage] controller, so a session-gated
route mistakenly placed there is shadowed: the anonymous shell renders,
the intended handler never runs, and the author sees a working page
rather than a 404. The shell exposes no data, so there is nothing to
exploit today - the hazard is that a routing slip becomes a silent auth
bypass instead of an error.
A comment cannot enforce that, so this adds both: the warning on the
route entry, and a contract test that reads the route table as source
text and asserts every /public route resolves to a method actually
declaring #[PublicPage] (and #[NoCSRFRequired], which a session-less
recipient needs). Injecting dashboard#summary at /public/oops fails it
with the route named. A negative control keeps the check honest.
Not done instead: rejecting requests that carry a valid Nextcloud
session. A logged-in user opening a recipient link is ordinary - the
sharer checking their own link, or a colleague with an account but no
vault suite - and that reject would break it. The rationale is recorded
in the test.
The coverage ratchet blocks this branch on PublicShellController, and the reason only became visible once PHPUnit itself stopped failing first. The guard scopes to changed PHP files and keeps head-only method buckets on the head side, so pageCatchAll() — two statements, added by this branch, with no test class anywhere for the file — entered the denominator uncovered and pulled the scoped ratio under the merge base. The other five changed lib/ files are comment and string edits inside methods that were already covered, so nothing else moved. Covering the file rather than the method: page() was untested too, and a shell at 0% is what let a new public method land uncovered without anything looking wrong. The assertions pin what would actually strand a recipient — RENDER_AS_BASE rather than the session-expecting user layout, the wasm-unsafe-eval directive the Argon2id derivation needs to run at all, and the identity of the two methods across the three live link shapes, since page() answers /public and pageCatchAll() answers everything below it and a divergence would break only one half with a correct-looking route table.
…ng-2 fix(routing): make public share, send and fill links reach the recipient
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.