Skip to content

feat(vault): detail sidebar, role-grouped toolbar and fleet-style selection - #479

Open
remko48 wants to merge 12 commits into
developmentfrom
feature/frontend-improvements
Open

feat(vault): detail sidebar, role-grouped toolbar and fleet-style selection#479
remko48 wants to merge 12 commits into
developmentfrom
feature/frontend-improvements

Conversation

@remko48

@remko48 remko48 commented Aug 28, 2026

Copy link
Copy Markdown
Member

Draft until the @conduction/nextcloud-vue release lands. The vault page consumes library features from ConductionNL/nextcloud-vue#832 and its follow-up (contextual selection strip, #before-collection/#after-search slots, card clickToView, the CnPageRenderer custom-page fix) that are not in the released package yet — locally the built dist is overlaid into node_modules. Once the release is out, a dep-bump/lockfile commit follows and this PR goes ready for review. Until then CI runs against the released package and the vault surface will not behave as described.

Restyle Stage 8 for the vault list, in three arcs:

Secret details in a right sidebar (edf85380 + l10n follow-ups): clicking a row opens the detail as an NcAppSidebar over the list instead of navigating away — Proton Pass/Passwork style, one scrolling pane, the action row in the header. /secrets/:id stays deep-linkable: the id is an optional segment on the list routes, so the list never remounts when a row opens. Folder switches show a loading state instead of flashing the previous folder's rows (0f69672d).

Toolbar and selection rework (20c5f901): the page's three action surfaces collapse into the actions bar — Refresh via the bar's built-in entry, the create/import and "My data" actions in the Actions overflow, the secret-type filter as a funnel button beside the search field (now also carrying the sort radios, and flipping to a filled primary glyph whenever filter or sort deviates from the defaults). The page title is visually hidden (h1 kept for accessibility) and the bar takes the freed top row, clearing the floating nav toggle. Selection is CnIndexPage's own in all three views — header select-all, row/card checkboxes, shift-range preserved through NcCheckboxRadioSwitch — shared across view switches, pruned to visible rows on every list change, with bulk actions living in the library's contextual selection strip (live count via role="status", WCAG 2.1 SC 4.1.3; bulk-actions §3.1's required bar). Vaults/folders render as a captioned strip above the collection in every view mode instead of masquerading as secrets among rows and cards. New l10n string "Filter and sort" in en + all 36 required locales; six English-copied strings and the secret-type labels got real translations across all locales (17988a77, 2e88165d, c12eb9d7).

Navigation fixes: the rail's "…" node — not a deep folder's visible ancestor — carries the selection whenever the open folder lives below the 5-level display cap (58082c6b, generalized in e2836c78).

Also: b5d5cbfc removes a dev-only auto-unlock block that accidentally rode along in the l10n commit (NODE_ENV-guarded and inert without an explicit localStorage opt-in, so it never shipped in a production build — removed from tracked sources regardless), and 41ca063a redraws the lock shackle as a filled outline in all five app icons so ImageMagick's stroke-less favicon rasterizer renders the full lock.

Review note: the toolbar-fold, funnel placement, selection strip and vault-strip decisions are recorded with their rationale in the Stage 8 sections of RESTYLE-PLAN.md (untracked working doc) and inline where each choice lives in the code.

remko48 added 12 commits August 27, 2026 15:36
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.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ 532f2cd

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 ✅ 550/550
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright 🚨 NO VERDICT — enabled but never ran
Hydra gates

Quality workflow — 2026-08-28 13:34 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ b9bec65

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 ✅ 550/550
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright 🚨 NO VERDICT — enabled but never ran
Hydra gates

Quality workflow — 2026-08-28 13:55 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant