Skip to content

feat: Edit items and folders, share edits, themes and layout in the browser extension - #1249

Open
SudoThijn wants to merge 9 commits into
developmentfrom
feature/browser-extension-2
Open

SudoThijn wants to merge 9 commits into
developmentfrom
feature/browser-extension-2

Conversation

@SudoThijn

@SudoThijn SudoThijn commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The second round of the Keepiq browser extension.

Editing, folders, theme and layout are done and walked through. Saving an edit of a shared item (ext-share-sync) goes through Keepiq's one-transaction share-save (#1254, #1258), has been reviewed in three rounds and passes every automated check; its walk-through in the browsers is still open, see Not done yet.

  • Editing (ext-vault-edit): add, edit, clone, move and delete items, including notes, additional fields and the totp, card and identity types, from a primary "New" button and the item's More menu.
    • All encryption happens in the background. Before it encrypts, it checks the server's active suite is still the cached one, and proves the certificate belongs to the unlocked key.
    • Edit fetches the item fresh and saves only what changed, refused when a newer version was saved elsewhere.
    • Shared copies keep their use-only, read-only and expiring limits. Delete moves an item to Keepiq's trash.
    • The form shows every field it saves: the username on every type, a note's notes, and a card or identity key that isn't JSON. Multi-line secrets open masked and keep their line breaks.
  • Sharing an edit (ext-share-sync): a change to the password, username or fields saves through PUT /api/v1/secrets/{id}/share-save, which writes the editor's row, every local copy and every partner share in one transaction, or nothing.
    • The first request carries empty lists; on 409 incomplete the background encrypts for every holder the server names and sends again, staging above 100 entries.
    • A team editor or delegate saves through their copy and reaches the owner and the other copies; partners get it with the owner's next save, and the item says so.
    • A copy without edit rights (no owner, write or manage grade from write-context) is view-only: Edit is unavailable on its detail with the reason, Clone and Move stay, and the background refuses to edit or save it. The grade comes from the server alone, so group members become editors once Members of a group a secret is shared with can't save changes for everyone #1285 lands.
    • Partners get the typed fields plus what the edit form showed, never a fresh row. Their certificates are verified in the background against the pinned partner root, a port of the web app's verifier that is stricter in places and runs the web app's whole fixture.
    • A certificate that fails, or a partner that no longer knows the recipient, pauses that share in the same save. An unreachable partner stops the save, and Save without them pauses it instead. Verified certificates are kept in memory for five minutes, so that doesn't look every partner up again.
    • A failed save keeps the form open with the reason. Nothing about a save outlives its request: no Try again records, kept warnings or save queue.
  • Folders: create, rename and delete under Settings → Vault → Folders, following Keepiq's three delete modes, and create one from the folder picker.
  • Theme (ext-theme): light, dark or system, under Settings → Appearance.
  • Layout: item detail and settings sections open over the tab bar, with their actions in a footer; the whole item row is clickable.
  • Refusals: the API client reads Keepiq's 428 refusals like a 403. This also fixes sync when an admin turned off offline caching, where the manifest answers 428.
  • Privacy: spell check is off on every popup field, so spelling services never receive vault text.

Not done yet

  • Walk-through of ext-share-sync against the dev server in Chrome and Firefox (task 4.5): a direct, a group and a team-folder recipient opening the new value in the web app, a write-grade editor, a read-grade viewer, a recipient added mid-save, and a partner recipient once a partner instance is set up.
  • A group member's edit, once Members of a group a secret is shared with can't save changes for everyone #1285 lands: today Keepiq gives a member of a group a secret is shared with no write grade, which is a backend bug, so the extension shows their copy as view-only. The extension needs no change for it, since it takes the copy-editor path whenever write-context answers write. Walk it through in Chrome and Firefox then: the owner and the other members must get the change.
  • Follow-up issues for two line-break gaps, drafted but not filed: an edit joins a line break stored in a single-line field (username, name, URL, field name), and mixed line-break styles come back as LF after an edit.

Server and web app follow-ups

Found during the reviews of this change and filed, since they need the server or the web app:

Once this is merged, PUT /api/v1/secrets/{id}/sync and PUT /api/v1/federated-shares/{id} have no client left (#1258).

How to test

cd browser-extension
npm ci
npm run dev            # or: npm run dev:firefox
  1. Unlock, press New in the Vault tab and save a login, then a card, an identity and a totp item. Open them in the Keepiq web app too.
  2. Edit, clone, move and delete an item from its detail and from the More menu.
  3. In Settings → Vault → Folders, create, rename and delete folders, including one with items and one with subfolders.
  4. Switch the theme in Settings → Appearance.
  5. Share an item with another user and a group, change its password in the extension, and open it as each recipient in the web app. Stop the server during a save to see that nothing is saved and the form keeps the change.

Checks

  • ✅ npm test (1,084 tests), npm run typecheck, npm run lint, npm run build and npm run build:firefox pass locally
  • ✅ Editing, folders, theme and layout walked through in Chrome and Firefox against the dev server
  • ✅ ext-share-sync reviewed in three rounds, with the security lens, until no finding was left
  • ⬜ Sharing an edit walked through in Chrome and Firefox (task 4.5)

Test plan

  • CI passes
  • Add, edit, clone, move and delete items in Chrome and Firefox
  • Create, rename and delete folders in each delete mode
  • Change a shared item's password, username and fields; check each recipient's copy, and that a failed save writes nothing

@SudoThijn SudoThijn changed the title feat: Edit items and folders, themes and layout in the browser extension feat: Edit items and folders, share edits, themes and layout in the browser extension Oct 13, 2026
@SudoThijn
SudoThijn marked this pull request as draft October 13, 2026 13:26
@SudoThijn
SudoThijn force-pushed the feature/browser-extension branch from c9a71dd to 0689bce Compare October 16, 2026 08:18
…tion (ext-theme)

Theme offers System default, Light and Dark, stored globally in `settings.global` so it also applies before an account is unlocked. The popup mirrors it in localStorage to paint the first frame in the right theme. The Settings tab lists its six sections; Appearance opens, the rest wait for ext-settings, which is trimmed to build on this change.
… actions

The open button's hit area now stretches over the row under the action buttons, so the hover covers the full row and hovering an action highlights only that button, as in Bitwarden.
… bar

As in Bitwarden, a page opened from a tab replaces the tab bar with a footer for its own actions, filled through `PageFooter`. The item detail's Edit and Delete move there; a page without actions shows no bar. The toast now sits above whichever bar is at the bottom.
…olders (ext-vault-edit)

The popup sends plaintext drafts and the background does all the encryption, after it proves the suite certificate matches the unlocked key and checks the server's active suite is still the cached one. Edits fetch the item fresh and save only what changed, with `baseUpdatedAt`; shared copies keep their use-only, read-only and expiring limits in the popup and the background. Folders get create, rename and Keepiq's three delete modes from Settings → Vault → Folders, always reading the server's children first.

The API client reads Keepiq's 428 refusals like a 403, which also fixes the manifest fallback when an admin turned off offline caching. Shared edits reaching recipients is recorded as ext-share-sync; the non-transactional folder delete is keepiq#1248.
… (keepiq#1248)

Keepiq now refuses a folder delete before it moves or purges anything, so the dialog no longer warns that items may already have moved. It still checks the names it can see before sending, and syncs on a 409 so that check can name the clash; ADR-003 and the ext-vault-edit design and spec say the delete is all or nothing.
… request (ext-share-sync)

A change to the password, username or fields of an item saves through Keepiq's `PUT /api/v1/secrets/{id}/share-save` (keepiq#1254), which writes the editor's row, every local copy and every partner share in one transaction, or nothing. The background reads `write-context` for the path, sends empty lists first, and on 409 `incomplete` encrypts the change for each holder the server names, staging above 100 entries. A copy holder without a write grade saves only their own copy. A failed save keeps the form open with the reason; nothing about a save outlives its request, so there is no Try again record, kept result or save queue.

Partner shares get the whole value: the typed fields plus what the edit form showed, never a fresh row. Their certificates are verified in the background against the pinned partner root (a port of the web app's verifier, stricter in places, tested against the web app's fixture). A refusal suspends that share, an unreachable partner stops the save until the user saves without them, and verified certificates are kept in memory for five minutes. The item's detail notes an own-copy save, partners left to the owner, and paused shares.

The edit form shows every field a save vouches for: the username on every type, a note's notes and a card or identity key that isn't JSON. Multi-line secrets open masked and keep their line breaks, and spell check is off on every popup field.
@SudoThijn
SudoThijn force-pushed the feature/browser-extension-2 branch from 37e67ad to e30b404 Compare October 16, 2026 11:36
Comment thread browser-extension/src/crypto/crypto.test.ts Dismissed
Comment thread browser-extension/src/vault/fields.test.ts Dismissed
Comment thread browser-extension/src/vault/fields.test.ts Dismissed
Comment thread browser-extension/src/vault/write.test.ts Dismissed
Comment thread browser-extension/src/vault/write.test.ts Dismissed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ d71b817

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 ✅ ✅ 114/114
npm ✅ ✅ 660/660
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit 🚨 NO VERDICT — enabled but never ran
Newman 🚨 NO VERDICT — enabled but never ran
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ⏭️

Quality workflow — 2026-10-16 11:38 UTC

Download the full PDF report from the workflow artifacts.

@SudoThijn
SudoThijn marked this pull request as ready for review October 16, 2026 11:50
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ d71b817

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 ✅ ✅ 114/114
npm ✅ ✅ 660/660
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅ 2321/2321 · 3 skipped
Newman ✅ 177/177 · 20 skipped
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-16 12:05 UTC

Download the full PDF report from the workflow artifacts.

…t-share-sync)

A copy whose write-context grade isn't owner, write or manage can no longer be edited. Before, the extension saved such an edit to that copy alone, leaving it out of step with the owner's and everyone else's. The item's detail asks `item.editable` and shows Edit as unavailable with the reason, while Clone and Move stay; the edit fetch and every save but a folder move refuse the copy with `view_only`, so the own-copy save and its note are gone.

The grade comes from the server alone, so group members become editors once Keepiq gives them the write grade (keepiq#1285), and a per-share permission (keepiq#1286) needs no client change. The web app's counterpart is keepiq#1287. privacy.md now also lists the edit check an item's detail sends and what a shared save sends.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ f14c470

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 ✅ ✅ 114/114
npm ✅ ✅ 660/660
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅ 2321/2321 · 3 skipped
Newman ✅ 177/177 · 20 skipped
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-16 13:31 UTC

Download the full PDF report from the workflow artifacts.

…at the end as a trash icon

As in Bitwarden, the item detail's footer has Edit on the left at its natural width, and the More menu and a red trash-can Delete on the right, as 40px icon buttons with 20px glyphs that read as a pair. Delete keeps "Delete" as its accessible name and tooltip, and an unavailable action is dimmed with its reason as before. The item form's Save and Cancel keep their full-width footer.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ 335163e

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 ✅ ✅ 114/114
npm ✅ ✅ 660/660
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅ 2321/2321 · 3 skipped
Newman ✅ 177/177 · 20 skipped
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-16 13:42 UTC

Download the full PDF report from the workflow artifacts.

…nt's (keepiq#1257)

A recipient's leaf with basicConstraints cA=TRUE is now refused with bad_chain, whatever its keyUsage says, as the web app does. Older certificates that carry it are not kept working, by decision on keepiq#1257.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/keepiq @ a9285ae

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 ✅ ✅ 114/114
npm ✅ ✅ 660/660
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅ 2321/2321 · 3 skipped
Newman ✅ 177/177 · 20 skipped
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-16 14:37 UTC

Download the full PDF report from the workflow artifacts.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants