Skip to content

feat: sidebar dashboard + Connect dialog (JDBC/SSH/RDP/copy-command)#8

Merged
valehdba merged 1 commit into
mainfrom
feat/connect-dialog-and-sidebar-redesign
May 9, 2026
Merged

feat: sidebar dashboard + Connect dialog (JDBC/SSH/RDP/copy-command)#8
valehdba merged 1 commit into
mainfrom
feat/connect-dialog-and-sidebar-redesign

Conversation

@valehdba

@valehdba valehdba commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

Reskins the vault page from a single flat grid into a Supabase-Studio-style sidebar dashboard, and adds the headline feature: a one-click Connect action per credential that bridges saved logins to the user's terminal, RDP client, or JDBC tool.

Screenshots

The dashboard with sidebar grouping (protocol / IP / port / user), engine-coloured rows, env tags, last-used relative time, and a Connect button per row:

Vault dashboard

The Connect dialog. Adapts per credential — for a Postgres entry it offers JDBC URL → SSH → copy-command → RDP. Each disabled cleanly when not applicable:

Connect dialog

The protocol-aware Add form — picking PostgreSQL auto-fills port 5432 and surfaces a Database field; picking Oracle adds Service name; picking RDP adds Windows AD domain:

Add credential

What's in the diff

Schema — backwards compatible

VaultLoginPlaintext gains five optional fields, all encrypted client-side like the rest of the credential body. Existing items keep working — when + "protocol" + is absent the UI infers it from the port (5432 → psql, 22 → ssh, 3389 → rdp, …).

Field Purpose
+ "protocol" + Drives Connect actions and the engine tile / protocol pill
+ "database" + DB connect-string suffix
+ "serviceName" + Oracle SERVICE_NAME (preferred over SID)
+ "domain" + Windows AD domain — used by the .rdp file builder
+ "environment" + Free-form prod/staging/dev/cache, surfaced as a row tag

+ "packages/web/src/connect/" + — pure, tested utilities

Six modules, 37 unit tests, no React deps:

  • + "protocol.ts" + — port → protocol inference, engine codes, default ports
  • + "jdbc.ts" + + "buildJdbcUrl" + for Postgres / MySQL / MariaDB / Oracle / MSSQL / Mongo. Returns null for protocols without standard JDBC drivers (SSH, RDP, Redis). Never embeds the password — JDBC tools want it separately.
  • + "command.ts" + + "buildConnectCommand" + produces + "psql 'postgres://...'" + , + "mysql -h ... -p" + , + "sqlplus '...'" + , + "redis-cli -a "$PASSWORD"" + , etc. Placeholders for the password, never the literal.
  • + "ssh.ts" + + "ssh://user@host:port" + URL builder + + "launchSshUrl" + to invoke the OS protocol handler.
  • + "rdp.ts" + + "buildRdpFile" + writes a Windows-format + ".rdp" + keyed for modern clients (RDP 8+); + "downloadRdpFile" + triggers a browser download via Blob + revocable object URL. Password is not embedded — RDP requires DPAPI/keychain for that, neither of which a browser can produce.
  • + "clipboard.ts" + + "copySensitive" + with a 30 s self-clear timer; checks the clipboard hasn't been replaced before wiping.

UI components

Vault page split into focused pieces under + "packages/web/src/pages/vault/" + :

  • + "Sidebar.tsx" + — logo, view filter, Vault list, Group sections (protocol / IP / port / user) with live counts, signed-in user card. Sidebar clicks set a scope filter that composes with the search box.
  • + "CredentialsGrid.tsx" + — 12-col grid with checkbox / engine tile / name + url subtitle / hostname (click to copy) / IP / user / password (mask + reveal/copy hover-revealed) / port / protocol pill / env tag / last-used relative time / Connect button / delete.
  • + "ConnectDialog.tsx" + — modal with up to four actions, each disabled cleanly when not applicable. Esc closes; backdrop click closes; the footer reaffirms zero-knowledge.
  • + "AddCredentialForm.tsx" + — protocol-aware: picking PostgreSQL auto-fills port 5432; picking RDP surfaces a Windows AD domain field; picking Oracle surfaces both Database (SID) and Service name (preferred) fields.
  • + "lastUsed.ts" + — local-only timestamps in localStorage, never sent to the server. Stays consistent with the zero-knowledge posture.

Styles

+ "styles.css" + is a from-scratch design system: dark surface tokens, signature green brand accent (#3ECF8E), per-engine swatches (PG / OR / MY / RD / MG / SSH / RDP), JetBrains Mono for technical fields, dot-grid background. Compiles to 19 KB / 4.5 KB gzipped. The auth pages ( + "auth-container" + ) get a coherent restyle — same dark panel + green primary button as the vault.

Verification

  • + "npm run typecheck --workspaces" + clean across core / web / extension
  • + "npm test --workspaces" + 62 passed (25 core + 37 web), 1 skipped (integration)
  • + "npm run build --workspace=@passman/web" + — clean (19 KB CSS, 75 KB JS gzipped)
  • Verified visually via Vite dev server + Claude Preview that auth pages render correctly with the new aesthetic

Docs

  • + "docs/USER_GUIDE.md" + rewritten — full walkthrough of the new dashboard, the Connect dialog with per-engine JDBC URL formats, and the protocol-aware add form.
  • + "docs/preview/" + mockups updated to mirror the new live DOM byte-for-byte (they + "" + the live + "styles.css" + , so any future style change auto-propagates to the screenshots when you re-capture).
  • + "docs/preview/design/" + keeps the four design-direction prototypes (Studio / Aurora / Sidebar / Cards) for future reference.

Out of scope (deliberate, for follow-up PRs)

  • SSH private-key field — the option is shown disabled in the dialog. v2 adds a PEM upload to the credential schema.
  • Edit existing credentials — current app supports add+delete only; this PR keeps that constraint.
  • Sortable column headers — visual indicator only; full sort wiring is a follow-up.
  • Bulk operations beyond delete — Copy passwords / Export .env / Move to favourites are mocked in the UI plan but not in this PR.

Test plan

  • Build clean
  • Typecheck clean across all workspaces
  • All unit tests pass (62)
  • Manual: register a fresh vault, log in, add a Postgres credential with database name, click Connect → verify JDBC URL is on clipboard
  • Manual: add an SSH credential with non-default port, click Connect → SSH session, verify default terminal opens with + "ssh user@host -p 2222" +
  • Manual: add an RDP credential with AD domain, click Connect → Download .rdp, open the file, verify mstsc/Microsoft Remote Desktop pre-fills hostname + + "DOMAIN\\user" +
  • Manual: ⌘K focuses the search; Esc closes the Connect dialog
  • Manual: select multiple rows, hit Delete in the selection toolbar
  • Manual: load an item created on a previous version (no protocol field) — confirm engine tile + protocol pill still appear via port inference

🤖 Generated with Claude Code

Reskins the vault page from a single flat grid into a Supabase-Studio-style
sidebar dashboard, and adds a one-click Connect action that bridges saved
credentials to the user's terminal, RDP client, or JDBC tool.

## Schema

`VaultLoginPlaintext` gains five optional fields, all encrypted client-side
like the rest of the credential body. Existing items keep working — when
`protocol` is absent the UI infers it from the port (5432 -> psql, 22 -> ssh,
3389 -> rdp, ...).

- `protocol` — drives which Connect actions appear and which engine tile
  + protocol pill render in the grid
- `database` — DB connect-string suffix
- `serviceName` — Oracle SERVICE_NAME (preferred over SID)
- `domain` — Windows AD domain for RDP, used by the .rdp file builder
- `environment` — free-form prod/staging/dev/cache, surfaced as a row tag

## Connect module (packages/web/src/connect)

Pure functions, no React. Tested with 37 unit tests:

- `protocol.ts` — port -> protocol inference, engine codes, default ports
- `jdbc.ts` — `buildJdbcUrl` for Postgres / MySQL / MariaDB / Oracle / MSSQL
  / Mongo. Returns null for protocols without standard JDBC drivers (SSH,
  RDP, Redis). Never embeds the password — JDBC tools want it separately.
- `command.ts` — `buildConnectCommand` produces `psql 'postgres://...'`,
  `mysql -h ... -p`, `sqlplus '...'`, `redis-cli -a "$PASSWORD"`, etc.
  Placeholders for the password, never the literal.
- `ssh.ts` — `ssh://user@host:port` URL builder + `launchSshUrl` to invoke
  the OS protocol handler.
- `rdp.ts` — `buildRdpFile` writes a Windows-format .rdp keyed for modern
  clients (RDP 8+); `downloadRdpFile` triggers a browser download via Blob
  + revocable object URL. Password is NOT embedded — RDP requires
  DPAPI/keychain for that.
- `clipboard.ts` — `copySensitive` with a 30 s self-clear timer; checks
  the clipboard hasn't been replaced before wiping.

## UI components

Vault page split into focused pieces under `packages/web/src/pages/vault/`:

- `Sidebar.tsx` — logo, view filter, Vault list, Group sections (protocol /
  IP / port / user) with live counts, signed-in user card. Sidebar clicks
  set a scope filter that composes with the search box.
- `CredentialsGrid.tsx` — 12-col grid with checkbox / engine tile / name +
  url subtitle / hostname (clickable to copy) / IP / user / password (mask
  + reveal/copy hover-revealed) / port / protocol pill / env tag /
  last-used relative time / Connect button / delete.
- `ConnectDialog.tsx` — modal with up to four actions (JDBC / SSH /
  copy-command / RDP), each disabled cleanly when not applicable. Esc
  closes; backdrop click closes; the footer reaffirms zero-knowledge.
- `AddCredentialForm.tsx` — protocol-aware: picking PostgreSQL auto-fills
  port 5432; picking RDP surfaces a Windows AD domain field; picking
  Oracle surfaces both Database (SID) and Service name (preferred) fields.
- `lastUsed.ts` — local-only timestamps in localStorage, never sent to the
  server. Includes `formatRelative` for "2m ago", "yesterday", "3d ago".

`VaultPage.tsx` orchestrates: search, scope filtering, selection,
toast, ⌘K-to-search, and bulk delete.

## Styles

`styles.css` is a from-scratch design system: dark surface tokens,
signature green brand accent (#3ECF8E), per-engine swatches (PG / OR /
MY / RD / MG / SSH / RDP), JetBrains Mono for technical fields, dot
grid background. Compiles to 19 KB / 4.5 KB gzipped.

The auth pages (`auth-container`) get a coherent restyle — same dark
panel + green primary button as the vault.

## Tests

- `packages/web/tests/connect.test.ts` — 37 tests covering URL builders,
  command builder, RDP file format (CRLF, full-address, domain encoding,
  no-password-leak), clipboard auto-clear behaviour.
- All existing core tests still pass (25/26, 1 skipped integration).
- `npm run typecheck --workspaces` clean across core / web / extension.
- `npm run build --workspace=@passman/web` clean.

## Docs

- `docs/USER_GUIDE.md` rewritten — full walkthrough of the new dashboard,
  the Connect dialog with per-engine JDBC URL formats, and the
  protocol-aware add form.
- `docs/preview/` mockups updated to mirror the new live DOM exactly.
  `docs/preview/design/` keeps the four design-direction prototypes
  (Studio / Aurora / Sidebar / Cards) for future reference.
- `docs/img/` regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valehdba valehdba merged commit ad3acd2 into main May 9, 2026
9 checks passed
valehdba added a commit that referenced this pull request May 9, 2026
Self-review of #8 surfaced six runtime/UX bugs that the test suite didn't
catch. Tests added for each.

1. handleJdbc had an empty `if (p.password) { /* comment */ }` block whose
   comment claimed to queue the password on the clipboard but did nothing.
   Removed the misleading code and updated the toast to be honest:
   "JDBC URL copied · use the row's Copy button for the password".

2. handleCommand copied the command and then copied the password, leaving
   the password on the clipboard while the toast said "command copied" —
   the user would paste the password instead of the command. Now copies
   the command alone; password lives on the per-row Copy button.

3. launchSshUrl assigned to `window.location.href`, which is unreliable in
   Chromium for custom-scheme URLs: when no handler is registered Chrome
   navigates the tab to an `ERR_UNKNOWN_URL_SCHEME` page, throwing the
   user out of the app. Switched to the synthetic-anchor-click pattern
   used by 1Password / Bitwarden — the click invokes the protocol handler
   if present and is a silent no-op otherwise.

4. The "Open RDP session" option was enabled for any credential with a
   host, then called `buildRdpFile({ ...item, protocol: "rdp" })` — which
   overrode the protocol but kept the credential's port. For a Postgres
   credential on port 5432 the resulting .rdp pointed at `host:5432`,
   which doesn't run RDP. The option is now gated through a new
   `canBuildRdp` helper that requires `effectiveProtocol === "rdp"`, and
   the disabled-state hint guides the user to set the protocol field.

5. VaultPage.toggleSelectAll compared `s.size === filtered.length` to
   decide between select-all and deselect-all. With cross-scope selection
   that comparison can be coincidentally true even when the visible rows
   aren't the selected ones, causing a deselect when the user expected a
   select. Switched to a per-row `every`-based check that matches what
   CredentialsGrid uses for the header-checkbox visual.

6. The Connect dialog's "where this points" subtitle ran a dedupe pass
   keyed on exact string equality — but "db-prod-01" and "db-prod-01:5432"
   are different strings, so the host appeared twice in the rendered
   header (visible in the v0 vault-connect screenshot). Extracted the
   logic into a tested `buildTargetSubtitle` helper that keeps host:port
   as the canonical segment and only adds the IP separately when the
   hostname was used as the primary identifier.

Tests: 48 passing (was 37) — +11 covering buildTargetSubtitle's dedupe
and canBuildRdp's gating, including the regression case where a Postgres
credential on port 5432 must NOT enable the RDP option.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
valehdba added a commit that referenced this pull request May 9, 2026
* fix(connect): six bugs in the new sidebar dashboard / Connect dialog

Self-review of #8 surfaced six runtime/UX bugs that the test suite didn't
catch. Tests added for each.

1. handleJdbc had an empty `if (p.password) { /* comment */ }` block whose
   comment claimed to queue the password on the clipboard but did nothing.
   Removed the misleading code and updated the toast to be honest:
   "JDBC URL copied · use the row's Copy button for the password".

2. handleCommand copied the command and then copied the password, leaving
   the password on the clipboard while the toast said "command copied" —
   the user would paste the password instead of the command. Now copies
   the command alone; password lives on the per-row Copy button.

3. launchSshUrl assigned to `window.location.href`, which is unreliable in
   Chromium for custom-scheme URLs: when no handler is registered Chrome
   navigates the tab to an `ERR_UNKNOWN_URL_SCHEME` page, throwing the
   user out of the app. Switched to the synthetic-anchor-click pattern
   used by 1Password / Bitwarden — the click invokes the protocol handler
   if present and is a silent no-op otherwise.

4. The "Open RDP session" option was enabled for any credential with a
   host, then called `buildRdpFile({ ...item, protocol: "rdp" })` — which
   overrode the protocol but kept the credential's port. For a Postgres
   credential on port 5432 the resulting .rdp pointed at `host:5432`,
   which doesn't run RDP. The option is now gated through a new
   `canBuildRdp` helper that requires `effectiveProtocol === "rdp"`, and
   the disabled-state hint guides the user to set the protocol field.

5. VaultPage.toggleSelectAll compared `s.size === filtered.length` to
   decide between select-all and deselect-all. With cross-scope selection
   that comparison can be coincidentally true even when the visible rows
   aren't the selected ones, causing a deselect when the user expected a
   select. Switched to a per-row `every`-based check that matches what
   CredentialsGrid uses for the header-checkbox visual.

6. The Connect dialog's "where this points" subtitle ran a dedupe pass
   keyed on exact string equality — but "db-prod-01" and "db-prod-01:5432"
   are different strings, so the host appeared twice in the rendered
   header (visible in the v0 vault-connect screenshot). Extracted the
   logic into a tested `buildTargetSubtitle` helper that keeps host:port
   as the canonical segment and only adds the IP separately when the
   hostname was used as the primary identifier.

Tests: 48 passing (was 37) — +11 covering buildTargetSubtitle's dedupe
and canBuildRdp's gating, including the regression case where a Postgres
credential on port 5432 must NOT enable the RDP option.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add hero screenshots to README — vault grid + Connect dialog

The README's "this is what it does" was an ASCII architecture diagram —
informative but it doesn't communicate the product. Added two screenshots
above the diagram:

1. The vault dashboard (sidebar groupings, engine-coloured rows, Connect
   button per row) — the at-a-glance "what does Passman look like?"
   answer for someone landing on the repo.
2. The Connect dialog with JDBC / SSH / copy-command / RDP options —
   the headline feature that distinguishes this from a generic password
   manager.

Both images already exist under docs/img/ and are kept in sync with the
live styles via the docs/preview/ mockups, so README screenshots track
the actual product without a separate maintenance burden.

Tightened the description's first line to mention the DBA / infrastructure
focus that the screenshots make obvious.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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