Skip to content

Develop#134

Merged
ParkerM2 merged 548 commits intomasterfrom
develop
Apr 27, 2026
Merged

Develop#134
ParkerM2 merged 548 commits intomasterfrom
develop

Conversation

@ParkerM2
Copy link
Copy Markdown
Owner

What

Why

Changes

Testing

  • npm run lint passes clean
  • npm run typecheck passes clean
  • npm run build succeeds
  • Manually tested in dev (npm run dev)

Screenshots

ParkerES and others added 30 commits April 19, 2026 13:14
Moves the 469-line createTestSuiteService factory into test-suite-service.ts
so index.ts is a barrel-only re-export, following FSD conventions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ers.ts

Aligns the handler orchestrator filename with FSD convention ({domain}-handlers.ts).
Updates import in ipc/index.ts, all 14 sub-handler files, and 3 doc comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iance

Renames 5 files (script-store, config-store, baseline-store, shared-steps-store,
screenshot-capture) to their correct *-service.ts names and updates all exported
interface/factory identifiers (ScriptStore→ScriptService, etc.) across
test-suite-service.ts, test-suite-handlers.ts, the unit test, and a doc comment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merged schema-baselines.ts, schema-schedules.ts, and schema-shared-steps.ts
into the main schema.ts, following the single-schema-per-domain convention
used by every other feature. Updated db barrel and all 4 importers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…criptSelector)

StepPanel superseded by StepList/StepTimeline, WebviewPanel superseded
by BrowserViewPanel, ScriptSelector had zero consumers. Verified via
grep before deletion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move screenshot-utils.ts from components/ to lib/ (utility, not a component) and move selector-builder.ts from main process to renderer lib/ (uses browser-only APIs). Update importers in ScreenshotPreview, ScreenshotThumbnailStrip, and selector-builder unit test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge analytics-schemas.ts, baseline-schemas.ts, and power-schemas.ts
into the single schemas.ts, following the single-file convention used
by all other domains. Update contract.ts, index.ts barrel, and two
renderer components to import from the unified source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o single source

Removes the locally-defined TestSuiteService and TestSuiteRunEvent interfaces
from test-suite-handlers.ts (which used unknown[] returns) and re-exports the
canonical strongly-typed versions from test-suite-service.ts. All 14 sub-handler
files continue to resolve TestSuiteService from '../test-suite-handlers' unchanged.
Also removes the now-unused InferZodType helper, locally-inferred QaRun/QaRunStatus/
QaRunReport/TestSuiteStep types, four Zod schema imports, and nine sub-service type
imports that only existed to serve the deleted local interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates 6 inline RunRecord definitions into a single canonical
interface at src/renderer/features/test-suite/lib/types.ts, with
optional fields to satisfy all consumers including RunLogDialog's
narrower shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… selectScript alias

- Remove duplicate StatusFilter from store; import/re-export from lib/constants
- Add StoreOutputLine and StreamOutputLine to lib/types; remove local definitions from store and useRunOutput
- Update ResultsOutputLog to import StreamOutputLine from lib/types instead of defining locally
- Remove selectScript back-compat alias (no external consumers found)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate DEFAULT_CONFIG_VIEWPORT_WIDTH/HEIGHT from starter-test.ts; import DEFAULT_VIEWPORT_WIDTH/HEIGHT from constants.ts
- Replace stepToLabel with describeStep as canonical step-to-label function; delete stepToLabel and shortTarget from format.ts
- Remove local toFileUrl from DiffViewer.tsx; import fileUrl from screenshot-utils.ts
- Fix text-text-muted typo to text-muted-foreground in format.ts
- Update ResultsPanel.tsx to remove unnecessary type cast on scriptSteps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ySchema

Adds companion type aliases for all major schemas in the IPC schemas barrel,
extracts TriggeredBySchema from inline enums in contract.ts and QaRunSchema,
tightens RunHistoryEntrySchema.status to QaRunStatusSchema, replaces seven
inline z.object({ success }) with SuccessResponseSchema in BROWSER-VIEW and
OPEN.REPORT channels, and updates the index.ts type export block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tSuiteKeys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…line key

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes nanoid dependency from 8 main-process test-suite files and replaces all nanoid() calls (including nanoid(8) in diff-engine.ts) with generateId() from @shared/lib/id, aligning with the project standard of crypto.randomUUID().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lineService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…usIndicator, MetadataList, Code, Stack)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves generateAssertionSuggestions to lib/assertion-suggestions.ts and
buildDefaultDescription (with its formatDuration dep) to lib/run-description.ts,
removing duplicate logic from SaveRecordingDialog and CreateTaskFromRunDialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, unused params

- Rename Props -> BrowserViewPanelProps in BrowserViewPanel.tsx
- Remove redundant title attr from RunSparkline dots (Tooltip already handles it)
- Change absolute @Renderer import to relative in ConfigEditDialog.tsx
- contract.ts doc comment already comprehensive (P2-T5), skipped
- Remove unused _specNames param from renderYaml() and update both callers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Maps the user's screenshotMode setting to Playwright's built-in screenshot
option: 'manual' → 'off', any active mode → 'on', unset → 'only-on-failure'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… status

Wires up the RUN.STEP IPC event through the full stack: adds stepType/status/durationMs fields to the contract schema, detects Playwright action lines in runner stdout via a pattern map + helper, forwards step events through TestSuiteRunEvent and the router, and updates useRunSteps to use payload.stepType directly and mark the previous step as passed on each new step arrival.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e fields

Adds AssertMethodSchema (8 assertion types) and optional attribute field to
QaStepAssertSchema; mirrors both additions in the QaStepAssert TS interface
with assertMethod optional for backward compat with existing scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ParkerM2 and others added 29 commits April 25, 2026 16:12
feat(peers): P2P sync phases 3a + 3b + 4 + 5 — full Hub removal
P2P sync rollout complete (Phases 3a + 3b + 4 + 5):
- Phase 3a: peer pairing primitives (PIN ritual, TLS, mDNS, peer-server)
- Phase 3b: renderer pairing UI (Settings → Peers, dialogs, hooks)
- Phase 4: extended SYNC_TABLES + workflow_runs_summary dual-write + op-log GC
- Phase 5: Hub server fully removed (renderer + main + Docker + nginx + certs)

This is a breaking change — the standalone Hub server no longer exists.
Multi-device sync is now direct peer-to-peer over TLS-pinned WSS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hub-tests job in test.yml referenced hub/package-lock.json which
no longer exists, failing all CI runs post-Hub-removal with "Some
specified paths were not resolved, unable to cache dependencies".

The release.yml had a guard (`if [ -f hub/package.json ]`) so it
silently skipped — that's why v0.3.0 still released cleanly. Removing
the dead stanza for hygiene.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI workflow fixes (drop hub-tests + hub build step) need a version
bump to release because the release workflow refuses to overwrite
existing tags. Patch bump since no user-visible behavior changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of v0.3.0 / v0.3.1 macOS startup crash:

  TypeError: Cannot read properties of undefined (reading 'get')
    at _interopNamespaceDefault (out/main/index.cjs:59)
    at Module.<anonymous> (out/main/index.cjs:69)

@peculiar/webcrypto was in devDependencies, so electron-vite's
externalizeDepsPlugin() didn't externalize it — Rollup inlined its
source. The bundled webcrypto does `import * as process from 'process'`
which Rollup wrapped with `_interopNamespaceDefault(require('process'))`.
At runtime, the for-in loop over Node's process global hit
prototype-inherited enumerable properties whose getOwnPropertyDescriptor
returned undefined, and `d.get` threw.

Fix: move @peculiar/webcrypto and @peculiar/x509 from devDependencies
to dependencies. They were always meant to be runtime deps (they back
the per-peer self-signed Ed25519 TLS cert generation in peer-tls.ts).
externalizeDepsPlugin now externalizes them, the bundled require('process')
disappears, and the crashing wrapper is no longer called.

Verified locally: the bundled require("process") call is gone post-fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… privkey from PeerIdentity surface

Audit refs: tmp/audit/01-security.md
- peer-identity.ts:39-43 — plaintext private key fallback no 0o600
- peer-identity.ts:73 — PeerIdentity.privkey exported but unused

Changes:
- Add IdentityOpts { allowPlaintext?: boolean } and require explicit
  opt-in when safeStorage.isEncryptionAvailable() is false. Throw
  before generating the keypair so failure path doesn't leak entropy.
  Error message instructs users to set
  ADC_PEERS_ALLOW_PLAINTEXT_IDENTITY=1 to opt in.
- Log a serviceLogger warning when writing in plaintext mode.
- Persist identity file with mode 0o600 (was default 0o644 minus umask).
  Mirrors peer-tls.ts pattern.
- Drop privkey from the exported PeerIdentity interface — only sign()
  is used by callers (verified via git grep: no consumers of
  PeerIdentity.privkey in src/main/features/peers or assistant). The
  on-disk JSON format is unchanged (still pubkey/privkey/useSafeStorage),
  only the in-memory surface is minimized.
- peers-service.ts: pass { allowPlaintext: process.env
  .ADC_PEERS_ALLOW_PLAINTEXT_IDENTITY === '1' } at the single call site.

Tests:
- New tests/unit/peers/peer-identity.test.ts (8 cases, 1 skipped on
  win32) — covers throw-without-opt-in, no-leak-on-failure-path,
  0o600 mode (POSIX), reload symmetry, sign() signature shape, and
  asserts 'privkey' not in identity.
- Update tests/integration/peers/peer-identity.test.ts to pass
  allowPlaintext: true (mock keeps safeStorage unavailable) and
  remove .privkey assertions; add a throw-without-opt-in case.

peer-tls.ts ripple: none. peer-tls.ts derives its own X.509
keypair via @peculiar/x509 and never reads PeerIdentity.privkey.
…rip peerIdShort from GC frontier, WALL_PAD=13
Audit 04 C1+C2: PeersService bootstrap was a fire-and-forget IIFE that
left handlers throwing "peers-service not yet initialized" if any IPC
call landed during the boot window, and disposePeerTransport was a no-op
if shutdown raced ahead of the IIFE — leaking the TLS listener + mDNS.

- Add wrapAsyncPeersService(): a Proxy that wraps Promise<PeersService>
  and returns Promise-shaped methods that await the inner promise before
  delegating. Source-compatible cast to PeersService at the wrap-site.
- Replace the IIFE in service-registry.ts with a single
  peersServicePromise + wrapper. disposePeerTransport now awaits the
  same promise so half-constructed resources still get cleaned up.
- Add a peersDisposed guard so a second dispose during the same window
  is a no-op.
- Update peers-handlers.ts comments — handlers continue to use
  Promise.resolve(...) which transparently chains the wrapper Promise
  for sync-typed methods (listPaired/listDiscovered/getIdentity/revoke)
  and keeps the existing await-thenable lint clean.
- Tests: 6 unit (wrapAsyncPeersService) + 3 integration (bootstrap-race)
  covering forward/race-safe/dispose-before-resolve/rejected-bootstrap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…edPeer source-of-truth

Closes audit H1-H5 from tmp/audit/04-service-ipc.md:
- H1: validatedHandle helper parses both input and output schemas
  (output validated only in non-production for zero prod overhead).
- H2: peers-handlers reaches through peersInvoke[channel] map instead
  of importing individual schemas.
- H3: HostnameSchema regex (DNS / IPv4 / IPv6 / zone-id / brackets)
  replaces arbitrary z.string() on host fields, closing SSRF surface.
- H4: displayName normalized to z.string().nullable() across
  DiscoveredPeerSchema, PairInitInputSchema, PairConfirmInputSchema,
  PinIssuedEventSchema. Drops .optional() permutations.
- H5: PairedPeer is now exported from @shared/ipc/peers/contract.ts;
  peer-store re-exports it. Single source of truth.

Also normalize DiscoveredPeerWithPaired.displayName to string|null
and coalesce ad.displayName ?? null in enrichDiscovered so output
schema validation passes in dev.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… visibility

Closes audit H4. Previously <IncomingPinDialog /> was mounted inside
SettingsPage, so a PIN issued by a remote initiator while the user was
on any non-Settings route was silently lost (the useIncomingPin hook
only listens while mounted). Hoisted the dialog to RootLayout alongside
other always-mounted overlays (AssistantWidget, WorkflowPermissionModal,
notifications). Mounted in the post-onboarding shell only — incoming
pair invites have no meaning before the workspace is initialized.

Removed the corresponding TODO(p2p-phase4) comment and the now-unused
IncomingPinDialog import from SettingsPage.

Backstop test added at tests/unit/renderer/peers/incoming-pin-global.test.ts.
The repo's vitest config uses environment: 'node' (no jsdom), so the test
falls back to static-analysis assertions that verify the dialog is imported
and rendered in RootLayout and absent from SettingsPage. To be replaced
with a render test if/when a jsdom-enabled vitest project is added.
…ePeerListPanel) + format helpers

Closes audit C3, C4, M2, M5 (and L3 partially) from tmp/audit/05-renderer.md.

- Extract useOutgoingPair hook owning Stage state machine, session token,
  PIN value (sanitized via sanitizePin), and pair-init/pair-confirm mutations.
  OutgoingPairDialog becomes render-only.
- Extract usePeerListPanel hook owning inviteTarget state and the four
  query/mutation hooks. PeerListPanel becomes render-only and the
  renderSelfBody / renderPairedBody / renderDiscoveredBody helpers are now
  typed sub-components <SelfBody/>, <PairedList/>, <DiscoveredList/>.
- Replace lib/truncate.ts with lib/format.ts adding peerLabel() and
  sanitizePin(); re-export truncate. Removes inline (peer.displayName ??
  truncate(peer.peerId)) duplication across all three dialogs.
- Add src/shared/ipc/peers/constants.ts with PIN_LENGTH=6 (M5) and
  PEER_ID_DISPLAY_MAX=16. format.ts and the hook import the shared
  constant; main + renderer agree on PIN size from one source.
- IncomingPinDialog (L3): replace Heading as="h1" for the PIN value with
  Text size="lg" + className="text-3xl font-mono tracking-widest"
  (Text size tokens are sm/md/lg only — visual scale comes from the
  Tailwind utility). Uses peerLabel() helper.
- index.ts: re-export the hooks, peerLabel, sanitizePin, truncate; drop
  truncate.ts barrel entry.

Tests (44 added/touched, all passing):
- format.test.ts (15 real unit tests on truncate/peerLabel/sanitizePin)
- useOutgoingPair.test.ts (10 static-analysis assertions, T13/T14 style)
- usePeerListPanel.test.ts (10 static-analysis assertions)

Verification: vitest peers/ green (44/44), npx tsc --noEmit clean,
npx eslint clean on all touched files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ase1 deprecations

Adds src/main/features/peers/peer-constants.ts as the single source of truth
for protocol-level + runtime constants shared across the peers module. Updates
consumers to import from there:

- peer-pairing.ts: SESSION_TTL_MS, SESSION_MAX_ATTEMPTS, SESSION_SOFT_LIMIT
  (drops local DEFAULT_TTL_MS / DEFAULT_MAX_ATTEMPTS / DEFAULT_MAX_ACTIVE_SESSIONS)
- pair-server.ts: PAIR_BODY_MAX_BYTES, PAIR_REQUEST_TIMEOUT_MS,
  PAIR_HEADERS_TIMEOUT_MS, PAIR_KEEPALIVE_TIMEOUT_MS, PAIR_BODY_READ_TIMEOUT_MS,
  LOOPBACK_HOST
- ws-transport.ts: WS_CLOSE_CODES (SCHEMA_MISMATCH, FINGERPRINT_MISMATCH,
  MALFORMED_FRAME, UNTRUSTED), MAX_INBOUND_SOCKETS, LOOPBACK_HOST
- peer-mdns.ts: MDNS_SERVICE_TYPE, MDNS_PROTOCOL, PEER_ID_SHORT_LEN
- peers-service.ts: GC_INTERVAL_MS, LOOPBACK_HOST
- outbound-dialer.ts: WS_RECONNECT_BASE_MS / WS_RECONNECT_MAX_MS /
  WS_RECONNECT_JITTER as defaults (still overridable via opts)

Removes the Phase1PeerConfig type alias and loadPhase1PeerConfig() function
from peer-config.ts. Grep across src/ + tests/ found zero non-self callers.

Pure mechanical refactor — no value changes, no behavior changes. Audit refs:
02-transport.md H2/H3/L4/L7, 01-security.md "magic numbers" section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…peer-state schema, drop peerId fallbacks, schema-hash uses node:crypto, cross-device-query DI, relocate peerKeys

- Move migration-tags.ts from src/main/features/peers/ to src/main/db/
  (audit 01/Low — generic Drizzle journal logic, not peer-specific).
- Clear revokedAt on re-pair in peerStore.upsert (audit 01/Medium —
  silent re-pair failure on previously-revoked peers).
- Merge peer-state-schema.ts into peers/schema.ts (audit 03/Low —
  one schema file per domain).
- Drop dev-default 'aaaaaaaa' / 'peer-a' peerId fallbacks in
  service-registry; resolve identity once via getOrCreatePeerIdentity
  and pass through to peers-service + replicationEngine. Throws if
  identity is unresolved (audit 04/L3).
- cross-device-query receives PeerStore via DI rather than
  constructing its own; reuses the registry-owned singleton
  (audit 04/M6).
- schema-hash.ts switches to node:crypto and is now sync — drops
  unnecessary async API at the only call site (audit 03/M8).
- Relocate peerKeys to @shared/ipc/peers/queryKeys.ts; renderer
  feature re-exports for source compat. EventBridge imports from
  @shared/ipc/peers, removing the boundaries/dependencies disable
  (audit 05/T13).

Tests: new tests/unit/peers/peer-store-revoked-reset.test.ts;
schema-hash + migration-tags + cross-device-query tests updated.

typecheck + lint pass on touched files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(peers): close 50+ audit findings across P2P sync system
@ParkerM2 ParkerM2 merged commit 38059b6 into master Apr 27, 2026
2 of 3 checks passed
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