Skip to content

Enhance documentation, linting, and production readiness for extension - #272

Open
guyghost wants to merge 156 commits into
mainfrom
develop
Open

Enhance documentation, linting, and production readiness for extension#272
guyghost wants to merge 156 commits into
mainfrom
develop

Conversation

@guyghost

Copy link
Copy Markdown
Owner

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.md to clarify the workflow boundaries, permissions, and local commands, as well as a full overhaul of .github/workflows/connector-health.yml to enforce stricter boundaries, explicit permissions, and reproducibility. Additionally, .github/workflows/release.yml is refactored to require explicit manual inputs and to package only pre-sealed candidates, further tightening security and reproducibility.

Documentation and Workflow Boundaries

  • Major rewrite of .github/workflows/README.md to 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.yml is completely restructured to:
    • Use three explicit jobs (health-capture, issue-writer, conclusion) with minimal and tightly scoped permissions.
    • Pin all action versions to exact SHAs for reproducibility.
    • Use exact Node and pnpm versions, verify source identity, and require frozen dependency graphs.
    • Transfer evidence only via immutable artifacts and require re-verification before issue creation.
    • Remove ad hoc health checks and replace them with a bounded, fixture-only protocol that never accesses production credentials or browser sessions.

Release Workflow Tightening

  • .github/workflows/release.yml is refactored to:
    • Remove automatic triggers on tags and require explicit manual inputs for source commit, expected version, and evidence artifact.
    • Only package and validate already-sealed candidates, never building or bumping versions during the workflow.
    • Downgrade permissions to read-only and pin tool versions for full reproducibility.

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:check
  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build

Checklist

  • No secrets, cookies, session tokens, or generated release artifacts committed
  • Core code remains pure; I/O stays in shell modules
  • Svelte changes use Svelte 5 runes only
  • Documentation updated when behavior or setup changes

guyghost and others added 30 commits July 12, 2026 11:27
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread .github/workflows/ci.yml
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" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread .github/workflows/ci.yml
run: |
[[ "$CAPTURED_TRANSPORT_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ "$UPLOADED_ARTIFACT_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ "$UPLOADED_ARTIFACT_SHA256" == "$CAPTURED_TRANSPORT_SHA256" ]]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines 763 to +765
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) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

guyghost and others added 5 commits July 28, 2026 17:57
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>
Copilot AI and others added 2 commits July 28, 2026 20:32
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
guyghost added 2 commits July 29, 2026 15:42
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
guyghost and others added 4 commits July 29, 2026 16:48
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)
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.

4 participants