Unify LocatorJS configuration with four-layer resolver (Phase 1)#213
Open
infi-pc wants to merge 2 commits into
Open
Unify LocatorJS configuration with four-layer resolver (Phase 1)#213infi-pc wants to merge 2 commits into
infi-pc wants to merge 2 commits into
Conversation
Collapse fragmented config surfaces (localStorage, chrome.storage, setup() args,
data-* HTML attributes, two settings UIs) into a single canonical type with a
strict per-key priority stack: default < team < user-extension < user-project.
Data model
- Canonical LocatorOptions in @locator/shared with split target fields
(targetId references targets map; targetTemplate is raw URL, wins over id).
- Pure resolve() returns {effective, provenance} and treats replacePath as an
atomic value (later layer fully replaces).
- resolveTarget() handles targetTemplate > targetId-in-map > first-target
fallback with kind discriminator for "unavailable on this site" UX.
Runtime
- optionsStore aggregates four layers via createMemo, exposes effective(),
provenance(), uiState(), allTargets(), setUserProject(), setUiState().
- Team layer is a live module-scoped signal so late setup() calls re-drive the
resolver after extension auto-init.
- Call sites simplified: linkTemplateUrl uses resolveTarget; buildLink reads
effective.projectPath (setInternalProjectPath deleted); modifiers use
effective.mouseModifiers (data-attribute fallback removed).
- welcomeScreenDismissed moved to nested uiState in user-project blob.
- popupBridge exposes window.__LOCATOR_RUNTIME__ + responds to snapshot and
site-local-write postMessage protocol; uses onCleanup for prod hygiene.
Extension
- Content script injects window.__LOCATOR_USER_EXTENSION_OPTIONS__ on page load
and on every chrome.storage change, plus postMessage broadcast (no mount race).
- snapshotBridge relays popup<->page requests with requestId + 1s timeout.
- Popup rewired on existing Hope UI (visual rewrite deferred to Phase 2):
unified syncedState over chrome.storage.local["userOptions"], polls active tab
snapshot, ProvenanceBadge per row, SiteLocalToggle for "set for this site only".
- Removed: tracking/social/clickCount UI + storage keys, requestEnable round-trip,
per-attribute data-* bridge, SharePage.
Cleanup, not migration
- Legacy LOCATOR_OPTIONS localStorage key + legacy chrome.storage.local keys
(target, controls, allowTracking, sharedOnSocialMedia, clickCount,
enableExperimentalFeatures) removed on first read. Users reset config on v2;
v2 is unreleased so no data-loss path.
Tests
- 30 shared tests: 16 resolver permutations + split-field target resolution +
atomic replacePath + cleanup idempotency.
- 11 runtime integration tests: late setup() live update, postMessage and
storage-event layer updates, user-project override semantics,
window.enableLocator, atomic replacePath, uiState isolation, team-targets
override, popup bridge snapshot + site-local-write protocol.
- vitest.config.ts in runtime resolves solid-js via browser conditions so
reactivity works in jsdom. Extension typecheck clean.
Phase 2 (separate PR): Kobalte + solid-ui rewrite of popover and popup,
Tailwind into Shadow DOM, packages/ui repurposed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
solid-icons 1.2.0 ships a packaging bug: sub-packages (e.g. solid-icons/bs)
do `import { IconTemplate } from '../lib/index.jsx'` via relative path, which
bypasses package.json `exports` and forces webpack to resolve the raw JSX file.
With babel-loader excluded from node_modules, the build fails to parse JSX.
Vite users avoid this because vite-plugin-solid runs babel across all of
node_modules; webpack/Rsbuild/parcel users hit it.
Switch the two usages to lucide-solid:
- `HiSolidCog` -> `Settings` from lucide-solid
- `BsGithub` -> inline SVG (lucide deliberately removed brand icons for
trademark reasons), matching the existing inline-SVG pattern in Home.tsx
lucide-solid ships a self-contained ESM bundle with zero relative-jsx imports,
so it works on webpack without any loader hacks. This unblocks the extension
build and keeps Phase 2 (Kobalte) free of node_modules babel-loader workarounds.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1 of v2 config unification. Collapses fragmented config surfaces (localStorage, chrome.storage,
setup()args,data-*HTML attributes, two settings UIs) into a single canonical type with a strict per-key priority stack:default < team < user-extension < user-project.UI rewrite is deferred to Phase 2 (separate PR). This PR keeps existing Hope UI popup and bespoke-CSS popover; visual polish is intentionally WIP since v2 is unreleased.
What changes
Data model (
@locator/shared)LocatorOptionswith split target fields (targetIdreferences targets map;targetTemplateis raw URL, wins over id).resolve()returns{effective, provenance}.replacePathmerges atomically (later layer fully replaces).resolveTarget()handlestargetTemplate>targetId-in-map > first-target fallback with a kind discriminator for "unavailable on this site" UX.Runtime (
@locator/runtime)optionsStoreaggregates four layers viacreateMemo, exposeseffective(),provenance(),uiState(),allTargets(),setUserProject(),setUiState().setup()calls re-drive the resolver after extension auto-init.linkTemplateUrlusesresolveTarget;buildLinkreadseffective.projectPath(setInternalProjectPathdeleted); modifiers useeffective.mouseModifiers(data-attribute fallback removed).welcomeScreenDismissedmoved to nesteduiStatein user-project blob.popupBridgeexposeswindow.__LOCATOR_RUNTIME__+ responds to snapshot and site-local-writepostMessageprotocol; usesonCleanupfor prod hygiene.Extension
window.__LOCATOR_USER_EXTENSION_OPTIONS__on page load and on everychrome.storagechange, plus postMessage broadcast (no mount-race window).snapshotBridgerelays popup↔page requests with requestId + 1s timeout.syncedStateoverchrome.storage.local["userOptions"], polls active tab snapshot,ProvenanceBadgeper row,SiteLocalTogglefor "set for this site only".requestEnableround-trip, per-attributedata-*bridge,SharePage.Cleanup, not migration
LOCATOR_OPTIONSlocalStorage key + legacychrome.storage.localkeys (target,controls,allowTracking,sharedOnSocialMedia,clickCount,enableExperimentalFeatures) removed on first read. Users reset config on v2 upgrade; v2 is unreleased so no data-loss path.Tests
replacePath+ cleanup idempotency.setup()live update, postMessage and storage-event layer updates, user-project override semantics,window.enableLocator, atomicreplacePath,uiStateisolation, team-targets override, popup bridge snapshot + site-local-write protocol.vitest.config.tsin runtime resolvessolid-jsvia browser conditions so reactivity works in jsdom.Total: 85 tests pass (30 shared + 55 runtime).
Not in this PR
packages/uirepurposed.master—solid-iconsships ESM+JSX innode_modulesand the currentbabel-loaderrule excludesnode_modules. Confirmed broken on a cleanmastercheckout. Out of Phase 1 scope.Test plan
pnpm --filter '@locator/shared' test→ 30 passpnpm --filter '@locator/runtime' test→ 55 passpnpm --filter '@locator/runtime' exec tsc --noEmitcleanpnpm --filter locatorjs-extension exec tsc --noEmitcleansetup({projectPath:"/foo"})after extension auto-boot updates link generation🤖 Generated with Claude Code