Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Establishes the root PRODUCT.md as the single source of truth for product strategy (register, users, purpose, brand personality, anti-references, design principles, accessibility targets) and persists the inaugural impeccable critique snapshot for the extension (24/40, 2xP0, 2xP1) so downstream /impeccable commands (polish, audit) can read the backlog. - PRODUCT.md: calm/precise/trustworthy brand, WCAG 2.1 AA targets - .impeccable/critique/2026-07-01T12-41-10Z__apps-extension.md: baseline heuristic scorecard + priority issues (contrast, FilterBar IA) - apps/extension/.impeccable/live/config.json: live-mode injection config Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Update PRODUCT.md and the feed-interaction spec to reference six platforms/job boards and list Malt explicitly, so docs match the connector registry. Addresses PR #182 review threads 3524638962, 3524638965, 3524638975. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mechanical/safe resolution of the 85 warnings introduced by the eslint-plugin-svelte 2.46 → 3.20 bump (#214), so the corresponding rules can run at their recommended severity ('error'): - svelte/require-each-key (73): add explicit keys to every each-block across 24 components (object id, primitive value, or index — always unique). - svelte/no-useless-children-snippet (9): unwrap redundant {#snippet children()} wrappers in BackupRestoreModal, ProfileSection and SettingsPage. - svelte/no-navigation-without-resolve (3): this rule is SvelteKit-only (expects resolve() from \$app/paths); the extension uses @crxjs/vite-plugin with no SvelteKit, so the rule is a false positive and disabled. eslint.config.js relaxation block now only keeps the two phase B rules (prefer-svelte-reactivity, prefer-writable-derived) at 'warn'; the three rules above are clean under recommended. Verified: 0 eslint errors, 0 typecheck errors, 2009 unit tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Phase B of the eslint-plugin-svelte v3 adoption: clear all 85 remaining reactivity warnings so the promoted recommended rules can run as errors. prefer-svelte-reactivity (84): - Convert `new Map()/Set()/Date()` to `SvelteMap/SvelteSet/SvelteDate` (from `svelte/reactivity`) across 10 files. - B1 (~76): local temporaries and `new Date()` -> direct replacement. - B2 (8): `$state`-wrapped collections that were reassigned via copy-on-write are refactored to non-`$state` `SvelteMap`/`SvelteSet` with in-place mutation (`.clear()`, `.set()`, `.add()`, `.delete()`). Five vars that are no longer reassigned switch from `let` to `const`. Affected: feed-controller (5), tracking (1), availability (1), cv-experience (1). prefer-writable-derived (1): - `ApplicationsPage.svelte` `nextActionInput` is genuinely writable (bound to a text input and reset on save); Svelte 5 has no writable `$derived`, so a scoped `eslint-disable` with justification is the correct, behavior- preserving fix. eslint.config.js: - Drop the `warn` relaxation for `prefer-svelte-reactivity` and `prefer-writable-derived`; both now run at the recommended level (error). Only the SvelteKit-only `no-navigation-without-resolve` remains disabled. Verified: lint 0/0, tsc 0 errors, prettier clean, 2009 tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Self-closing <option /> inside <datalist> emitted Svelte element_invalid_self_closing_tag warnings during build. Use <option ...></option> in ProfileSection and OnboardingWizard location catalogs.
* docs(feed): model stable mission arrivals * docs(feed): plan stable mission arrival stack * feat(feed): model stable mission arrivals * fix(feed): require continuous visibility before seen * feat(feed): expose buffered mission previews * fix(feed): keep new mission queue stable * feat(feed): add anchored mission arrival stack * fix(feed): stabilize buffered preview synchronization * test(feed): verify stable mission arrivals * fix(feed): address mission arrival review feedback --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…tate The dev-mode stub fires SCAN_COMPLETE after ~800ms and dispatches a `dev:missions` event that overwrites the feed with mock missions. When the test clicked "empty" before the scan settled, the scan raced back and overwrote the empty state, causing the assertion to fail. Follow the same pattern already used in `injectMissions`: poll until the initial scan has populated the feed (getMissionTotalCount > 0), wait an additional 900ms for the scan to fully settle, then click "empty" so no pending scan can race the assertion.
…accourci Améliorer la lisibilité et l’accessibilité du modal de raccourcis
…ibility PR #230) New commits on develop: - feat(ui): improve keyboard shortcuts modal accessibility (5f5da67) - chore(extension): format keyboard shortcuts help (e4b43fb) Conflict in KeyboardShortcutsHelp.svelte resolved by: - Keeping SvelteMap (from our branch, required by svelte/prefer-svelte-reactivity) - Taking the full accessible redesign from develop (dialogElement/closeButton state, focus trapping, aria-modal, aria-labelledby, aria-describedby) - Adding {#each} keys (category) and (index) required by svelte/require-each-key New files from develop taken as-is: - src/models/keyboard-shortcuts-help.model.md - tests/unit/ui/KeyboardShortcutsHelp.test.ts pnpm lint -> 0 errors · pnpm typecheck -> 0 errors
fix(lint): resolve all eslint-plugin-svelte v3 warnings (170)
Sync owner-view LinkedIn experience identities and mission arrival flow
sortMissions sits on the feed's reactive path (sortCurrentMissions in feed-page.svelte.ts), so it runs on every missions/filter/sort change. Previously the comparator recomputed each mission's sort key on every comparison — O(n log n) evaluations of a property chain (scoreBreakdown?.total ?? semanticScore ?? score ?? 0), a ?? 0 coalesce, or an instanceof Date + getTime() pair. Switch to decorate-sort-undecorate: precompute a numeric key once per mission (O(n)), sort the decorated pairs, then unwrap. Output ordering is identical (descending, stable for equal keys) — verified by the existing 8 unit tests, which all pass unchanged. Pure core change only; no API or behavior change.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 062620d936
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| name: Build, exercise, authorize and capture the sealed transport | ||
| run: | | ||
| "$GITHUB_WORKSPACE/node_modules/.bin/tsx" \ | ||
| "$GITHUB_WORKSPACE/apps/extension/scripts/build-sealed-candidate-transport.ts" \ |
There was a problem hiding this comment.
Add the sealed-candidate builder before invoking it
The seal-candidate job always invokes apps/extension/scripts/build-sealed-candidate-transport.ts, but a repo-wide search and inspection of apps/extension/scripts/ confirm that this file is not present in the reviewed commit. Every manual run on main therefore stops with a module-not-found error before producing the evidence artifact required by release.yml, making the release path unusable.
Useful? React with 👍 / 👎.
| run: | | ||
| [[ "$CAPTURED_TRANSPORT_SHA256" =~ ^[0-9a-f]{64}$ ]] | ||
| [[ "$UPLOADED_ARTIFACT_SHA256" =~ ^[0-9a-f]{64}$ ]] | ||
| [[ "$UPLOADED_ARTIFACT_SHA256" == "$CAPTURED_TRANSPORT_SHA256" ]] |
There was a problem hiding this comment.
Compare like-for-like artifact digests
This comparison can never validate a normal upload: transport-sha256 hashes the raw transport file, while steps.upload.outputs.artifact-digest hashes the artifact archive created by actions/upload-artifact. The pinned action's declared inputs and outputs do not include archive, so archive: false does not disable that wrapping. Once the missing builder is supplied, every otherwise successful seal-candidate run will fail here; hash the downloaded archive or compare the raw file after downloading it instead.
Useful? React with 👍 / 👎.
| @@ -55,7 +61,7 @@ function initShellBoot() { | |||
| Promise.all([ | |||
| chrome.runtime.sendMessage({ type: 'GET_PROFILE' }) as Promise<MessageResponse>, | |||
| chrome.runtime.sendMessage({ type: 'GET_FIRST_SCAN_DONE' }) as Promise<MessageResponse>, | |||
| chrome.runtime.sendMessage({ type: 'GET_ONBOARDING_COMPLETED' }) as Promise<MessageResponse>, | |||
| chrome.runtime.sendMessage({ type: 'GET_SETTINGS_RELEASE' }) as Promise<MessageResponse>, | |||
There was a problem hiding this comment.
Route the settings bootstrap request through the bridge
The new settings read sends an untyped Chrome message directly from the side panel, bypassing the centralized bridge logging and the strict validateMessage path already provided by settings-release.facade.ts. This lets bootstrap protocol handling diverge from the mounted application as the settings schema evolves; expose the bootstrap-safe read through the bridge/facade instead. The repository explicitly requires all inter-context messaging to pass through bridge.ts.
AGENTS.md reference: AGENTS.md:L113-L117
Useful? React with 👍 / 👎.
| with: | ||
| artifact-ids: ${{ needs.health-capture.outputs.artifactId }} | ||
| path: apps/extension/output/connector-health | ||
| merge-multiple: false |
There was a problem hiding this comment.
Extract the evidence artifact into the path the writer reads
When a failed capture admits the issue-writer job, selecting the artifact through artifact-ids uses the action's multiple-artifact directory layout; with merge-multiple: false, the evidence is extracted beneath a connector-health-report/ subdirectory. issue-writer-cli.ts instead opens output/connector-health/connector-health-evidence.v1.json, so every admitted connector failure reaches a missing-file error and no issue is settled. Enable merging for this ID-selected download or update the writer to read the artifact-name subdirectory.
Useful? React with 👍 / 👎.
| const settings = await getSettings(); | ||
| await setSettings({ ...settings, enabledConnectors: [...updated] }); | ||
| const wasEnabled = settings.enabledConnectors.some((connectorId) => connectorId === id); | ||
| const enabledConnectors = CANONICAL_INCLUDED_CONNECTOR_IDS.filter((connectorId) => { |
There was a problem hiding this comment.
Serialize connector toggles before deriving the next settings
When a user toggles two source rows before the first settings mutation settles, both invocations can read the same confirmed snapshot and independently derive candidates from it. The revision-gated release protocol then either rejects the second mutation as stale or accepts it against the newer revision while reverting the first candidate; the empty catch makes the lost change silent. The previous optimistic local set included the first click in the second candidate, so this is a regression for rapid multi-source edits; queue/rebase these mutations or disable all source toggles while one is pending.
Useful? React with 👍 / 👎.
Replaces the legacy single-card wizard with a model-first onboarding
flow following Model → Review → Implement → Verify.
Model (src/models/):
- onboarding-flow.contract.ts: pure types, validators, step guards
(canAdvanceStep), phase projection (phaseFromStateValue)
- onboarding-flow.logic.ts: factory createOnboardingFlowSetup(isAdmittedEvent)
receiving the admission-set reader from the machine module
- onboarding-flow.machine.ts: XState v5 machine + controller facade
owning the single ACTIVE_FLOW_EVENTS WeakSet (writer + reader) so
admitted events always match the guard
- onboarding-flow.model.md: authoritative prose model + invariants
Flow (welcome → connecting → wizard → notifying → persisting →
scanning → completed) with SKIP → scanning escape. Pure
OnboardingFlowEffect descriptors (PERSIST_PROFILE, START_SCAN) are
consumed by the shell; the machine never performs I/O. Re-entry is
forbidden (final states); partial-profile scans always run ("never
block first value").
UI:
- OnboardingWelcome.svelte: outcome-led hero, single CTA
- OnboardingFlow.svelte: renders all phases from the machine snapshot,
reports intent via onEvent
- OnboardingPage.svelte: rewritten as machine-driven shell that
subscribes, runs pendingEffect, reports back, calls onComplete
- ProfileChecklistPill.svelte: dismissible completion pill on the feed
Atoms (packages/ui):
- SegmentedControl.svelte: exclusive select (radiogroup), iOS pill
- ChipGroup.svelte: multi-select (role=group)
Verify:
- 20 new model tests (allowed/forbidden transitions, invariants,
re-entry guard, partial-profile scan, terminal idempotency)
- The suite caught a real Phase 1 bug: a duplicate WeakSet in logic.ts
desynced from the machine's guard set, silently rejecting every
event. Consolidated to one set owned by the machine module.
- typecheck clean; lint 0 errors; 3994 tests pass (+20)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- OnboardingFlow: Continue button now disabled solely by connectedSources.length === 0 (canAdvance mixed wizard-step guards with source selection and silently guard-blocked NEXT). - OnboardingPage: dispose feedController on unmount to stop the bridge listener leaking across navigations. - flow contract/machine/model: remove the `skipped` terminal phase (dead code — every SKIP transition targets `scanning` -> `completed`); the sole terminal is `completed`, matching the executable machine. - flow contract: accept an empty sources catalog (degenerate build where every connector is excluded) so the flow can still reach `completed` via SKIP instead of crashing during controller construction. - ProfileChecklistPill: correct the docstring — dismissal persistence is the parent host's responsibility, not this presentational atom's. - ChipGroup: drop the broad `as readonly string[]` / `as unknown` casts by using a Svelte 5 generics script; value/option types now flow. - SegmentedControl: implement the WAI-ARIA radiogroup pattern (roving tabindex + ArrowLeft/Right/Home/End keyboard nav) so the role is backed by real keyboard interaction. Tests: 22 onboarding-flow machine tests pass (was 20; +2 input-validation cases, empty-sources case now asserts the degenerate build is accepted). 3996 extension unit tests pass; 26 pre-existing failures are isolated to release-artifact/canonical-artifact scripts (native binary attestation, environmental, untouched by this PR). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Threads A,C,D,E,F,H,L from chatgpt-codex review: - Persist alert preference for BOTH notify states (disabled was skipped, leaving the enabled default active). (A) - Admit UPDATE_PROFILE in welcome/connecting so the shell can rehydrate an existing profile before the user advances. (C) - Write connected sources to settings.enabledConnectors so the scanner filters by the user's selection. (D) - Add `autoLoad` option to createFeedController; onboarding passes `autoLoad: false` so a scan is not fired before the user consents. (E) - Use event-based oninput (e.currentTarget.value) for identity inputs; the stale bound value caused the previous keystroke to be sent. (F) - Respect the user's explicit tjmMax instead of widening it to tjmMin+100. (H) - Await onComplete and surface a retry when finalization fails instead of leaving the user on "Redirection...". (L) Adds model tests for welcome-state profile rehydration and tjmMax preservation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Threads I, J from chatgpt-codex review: - Load ProfileChecklistPill on its own condition (profileCompletion < 100) instead of bundling its lazy import into loadRefinementBanner(). Previously the pill never loaded when the banner was dismissed or not yet needed, even though its render condition was true. (I) - Persist the pill's dismiss state across reloads. It now shares the existing profile-completion nudge flag (getProfileBannerDismissed), so dismissing the pill or the banner suppresses both — they serve the same intent. (J) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Commit 13fb673 ('merge: resolve conflict with origin/develop in background/index.ts') committed apps/extension/src/background/index.ts with the git conflict markers still present (<<<<<<< HEAD / ======= / >>>>>>> origin/develop), which broke prettier format:check for every PR targeting develop. Resolve by keeping the HEAD side (persist seen ids for notified missions before the notification is shown) and dropping the empty origin/develop side, matching the equivalent logic on feature branches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The function used `settingsSnapshot` in its body (to call `notifyHighScoreMissions` and drive the badge count from notifiable missions) but the parameter was absent from its signature. This caused `TS2304: Cannot find name 'settingsSnapshot'` in the typecheck step of CI. - Add `settingsSnapshot: SettingsReleaseSnapshot` as second parameter - Update the call site to pass the already-in-scope `settingsSnapshot` - Restore the notification-driven badge logic (use `notification.notifiableMissionIds.length` as badge count, save seen IDs for notified missions) from the original HEAD branch intent
…design feat(onboarding): redesign flow with machine-driven model
- Add qa-hardening.model.md as cross-cutting behavior source of truth - Fix filter panel visibility, stacking context and a11y sync - Sync Settings activation state and favorites count refresh - Apply operational UI constraints across pages and organisms - Update related unit and e2e tests
Resolve multiple UI issues on the Applications (Suivi) tab identified during a polish pass: Spacing & rhythm - Bump section gaps from mt-3/mt-4 (12-16px) to mt-5 (20px) between AvailabilityPanel, Pipeline section, and the missions grid. - Increase Pipeline section padding (py-4 -> py-5) for breathing room. - Flatten the nested "Dossier recommande" card (bordered card-in-card) into a subtle bg-blueprint-blue/5 tint, removing the nested-card smell. Contrast (WCAG AA) - Replace text-text-muted (#a6a09b, ~2.8:1 on white) with text-text-subtle (#57534d, ~7.3:1) across all Suivi-flow body text, labels, hints, and meta. Eliminates every low-contrast text node on the tab. Readability - Bump text-micro (10px) to text-caption (11px) for all eyebrows, status badges, evidence labels, hints, and meta throughout the Suivi flow (ApplicationsPage, ApplicationPipelineSummary, AvailabilityPanel, CopilotPanel, OperationalStoryCard, OperationalEmptyState, OperationalStatusBadge). 10px was below the comfortable readability threshold for a 400px side panel. Mobile - Remove the nested max-h-[32rem] overflow-y-auto scroll box on mobile; restrict it to lg breakpoint only so the whole page scrolls naturally on narrow viewports. Verified at 400px side-panel width: 0 horizontal overflow, 0 text below 11px, 0 muted-gray text nodes, consistent 20px section rhythm. Checked both list-only and mission-selected (CopilotPanel rendered) states. Typecheck and lint pass (0 errors).
fix(ui): polish Suivi tab spacing, contrast, and readability
Introduces a content-script-based form assistant that detects eligible inputs on known connector platforms and proposes field values derived from the local user profile via Gemini Nano (on-device, free). Phase 1 is local-only with no backend; remote Eve engine is reserved for Phase 2. Core (pure, no I/O): - types/classify-field/sanitize-field-descriptor/select-engine - build-field-prompt/parse-field-proposal/redact-for-remote - 45 mock-free unit tests Shell: - settings.ts: isolated chrome.storage.local key (formAssist) - local-generator.ts: Gemini Nano proposal (8s timeout + retry) - 3 SW handlers (Machine B, local path only) - bridge messages + Zod .strict() (7 FORM_ASSIST_* messages) Content script (Machine A): - field-detector: DOM -> FieldDescriptor pipeline - widget: closed Shadow DOM, 4 states, viewport-aware anchoring - index.ts: focus detection, bridge, native-setter applyValue (React/Svelte compatible) Wiring: - meta.ts: formAssist?: boolean (Free-Work pilot opt-in) - vite.config.ts: content_scripts.matches derived from catalog - dev stubs: storage key + mocked proposals per FieldKind Models/specs: - form-assistant.model.md (3 machines A/B/C), review matrix, openspec proposal Follows Model -> Review -> Implement -> Verify. The LLM produces signals; the model decides. No auto-submission: values are applied only on explicit user acceptance. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- content/index.ts: fix FORM_ASSIST_STATUS response read — the SW returns
a bridge message { type, payload }, so read result.payload.enabled
(was result.enabled, leaving the assistant stuck disabled).
- content/index.ts: fix stale model path reference in doc comment
(formassistant -> form-assistant).
- core/build-field-prompt.ts: drop literal '""' from email/phone/linkedin
instructions to avoid the LLM returning the quotes verbatim; ask for an
empty string with no characters instead.
- dev/chrome-stubs.ts: return empty strings for email/phone/linkedin to
mirror production (profile carries no such fields; avoids fake PII in
dev UX validation).
- models/form-assistant.model.md: align placeholder cap doc with the
real implementation (<=200, not <=120; label stays <=120).
- content/widget.ts: fix typo 'Isoled' -> 'Isolé'.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Widget & DOM:
- Click delegation via closest('[data-action]') (fixes nested span clicks)
- Right-edge viewport clamp in anchorTo (prevents overflow)
- Skip widget-internal focus events (isHostElement guard)
- Document mousedown listener for outside-click dismissal (shadow DOM safe)
- Multi-ID aria-labelledby resolution in field-detector
- Exclude readOnly/disabled/aria-readonly/aria-disabled fields
Orchestration (Machine A):
- applyValue returns boolean; failure shows error view instead of hiding
- Track activeRequestId; cancel in-flight SW request on field switch/dismiss
- Stale-response guard also checks activeRequestId match
Cancellation (model-mandated via AbortSignal):
- FORM_ASSIST_CANCEL message + FORM_ASSIST_CANCEL_ACK (Zod strict schemas)
- SW AbortController map; abort -> 'cancelled' error code
- local-generator uses promptWithCancellation/throwIfAborted/abortableDelay
(mirrors semantic-scorer)
Prompt & parsing:
- Include profile.availability in build-field-prompt
- Reject literal "" sentinel in parse-field-proposal
- Strip one level of surrounding quotes from LLM output
- Org/user marker guard in classify-field ("Nom de l'entreprise" -> free-text)
Performance:
- Bounded in-memory cache in local-generator (64 entries, FIFO)
Broadcast:
- FORM_ASSIST_ENABLED now also reaches content scripts via chrome.tabs.sendMessage
Tests: +11 cases (org marker guard, quote stripping, availability injection,
keyword cap, determinism). 56/56 form-assistant tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
feat(form-assistant): local Grammarly-like field assistant (Phase 1)
This pull request makes significant improvements to the documentation and implementation of the GitHub Actions workflows for the repository. The main changes include a complete rewrite and expansion of
.github/workflows/README.mdto clarify the workflow boundaries, permissions, and local commands, as well as a full overhaul of.github/workflows/connector-health.ymlto enforce stricter boundaries, explicit permissions, and reproducibility. Additionally,.github/workflows/release.ymlis refactored to require explicit manual inputs and to package only pre-sealed candidates, further tightening security and reproducibility.Documentation and Workflow Boundaries
.github/workflows/README.mdto clarify the boundaries between CI, release, and connector health workflows, explicitly stating what each workflow does and does not do, and providing detailed local command instructions. The documentation now emphasizes reproducibility, artifact evidence, and explicit permissions.Connector Health Workflow Overhaul
.github/workflows/connector-health.ymlis completely restructured to:health-capture,issue-writer,conclusion) with minimal and tightly scoped permissions.Release Workflow Tightening
.github/workflows/release.ymlis refactored to:These changes make the workflows more secure, reproducible, and auditable, and ensure that only explicitly authorized and pre-validated artifacts can be packaged and released.
Summary
Verification
pnpm format:checkpnpm lintpnpm typecheckpnpm testpnpm buildChecklist