π Reshape highlight modes (rainbow, click feedback, β₯-peek) + drop env config + passive edit mode#15
Merged
Conversation
Reshapes the highlight modes around the daily-driver workflow and adds
two distinct visual signals (hover vs. clicked).
Mode mental model
-----------------
selection (default)
Pristine page. Hover and click highlight the targeted component.
HOLD β₯ (Alt/Option) β corner accents appear on every component
for the duration of the press, so you can quickly scan for
structure without making the highlight permanent.
editable
Always-on corner accents on [data-editable]. Unchanged.
all
Always-on corner accents on every component. (Restored β was
pristine-by-default in the previous iteration; that was the wrong
default for an "all" mode and the peek behavior moved up.)
off
Nothing painted. Hover/select still highlight individually.
Why it works
------------
'selection' is now the smart default that covers the 90% case: the
page reads as untouched, hover and click work normally for inspection,
and β₯ is a power-user gesture for the occasional "where are all the
components?" peek. Users who want the always-on overview pick 'all';
users who want zero noise pick 'off'.
Click feedback (selected β hover)
---------------------------------
Hover and selected used to be visually similar β both 2px solid blue,
just 70% vs 100% opacity. After clicking a component you'd often
think "did anything happen?" The new selected rule adds an inset
box-shadow at 8% blue that fills the box, so selected = outline +
filled background. This reads as "this is the active item" the way
macOS Finder / GitHub file browser highlight rows. Implemented as
box-shadow inset (no extra pseudo, no layout impact, doesn't touch
the host's actual background).
Hover surfaces the component name
---------------------------------
The label badge ::before now renders for both `[hover][label]` and
`[selected][label]`, so mousing over a component shows what it is
without you having to click. Both rules paint identically when an
element is both hovered and selected, no flicker.
Edit-mode bypass
----------------
isEditMode() in clay-uri.ts: returns true for `?edit=true` exactly
(documented Clay convention; loose matching would silently disable
the extension on URLs that mention an edit param for unrelated
reasons). Bootstrap and the PANEL_TOGGLE handler both bail out, so
on edit pages we don't paint anything, don't mount the panel, and
don't send CLAY_DETECTED β the toolbar icon stays at its default
("not active here") popup.
Files
-----
- src/lib/clay-uri.ts β new isEditMode() helper.
- src/content/index.ts β bootstrap and PANEL_TOGGLE skip on edit mode.
- src/content/highlighter.ts:
* Corner-tick selectors gated by selection+reveal | all | editable.
* Selected gets inset 9999px box-shadow at TOKENS.selected.fillAlpha
(0.08) for click feedback.
* Hover gets position:relative + label-badge selector extended to
[hover][label]::before, so the component name surfaces on hover.
* installAltRevealListener() now triggers on mode='selection' (was
'all').
- src/lib/types.ts β labels: selection 'Selection' / all 'All
components'; descriptions updated to match.
- src/content/panel/components/HighlightModeMenu.tsx β compactLabel
shows 'Sel β₯' (was on 'All').
- README.md β Highlights blurb + keyboard table updated; new bullet
for the edit-mode bypass.
Tests (160 total, +10 new vs the previous PR baseline)
------------------------------------------------------
- isEditMode: ?edit=true / mixed-with-other-params (true), and the
five common false cases (false/1/no-value/empty/hash).
- Stylesheet contract:
* mode='selection' rules MUST include [data-clay-slip-reveal].
* mode='all' rules MUST NOT include the reveal attr.
* mode='editable' rules MUST NOT include the reveal attr.
* No bare 'mode="selection" [' selectors leak through.
* Hover + selected label badge selectors both present.
* Selected rule includes 'box-shadow: inset' (click feedback).
* Hover rule does NOT include the inset fill.
* Hover rule includes 'position: relative' (label badge anchor).
- installAltRevealListener: fires on 'selection' (was 'all'), no-op
on every other mode.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two requested adjustments on top of selection-peek + edit-mode bypass:
**1. Rainbow ambient outlines for `all` mode (and the β₯-peek)**
`all` mode and the selection-mode β₯-peek now paint every component with
a continuous full-perimeter outline cycled from a six-color palette,
matching the look the original Clay devtools shipped with β easier to
read at a glance as a structural map of the page than the corner
accents.
- `applyHighlights` now stamps `data-clay-slip-color-idx="0..5"` on
every component, cycled by document order.
- New `PALETTE` constant (six Tailwind 500-shade colors picked for
mutual distinguishability and for not clashing with the inspection
blue).
- Six per-color CSS rules per active mode key off
`[data-clay-slip-color-idx="N"]`.
- `editable` mode keeps the corner-tick rendering β different purpose
("show me what's editable" vs. structural overview).
- Hover and selected still paint in the single blue accent (outline +
inset tint) so the click-feedback signal stays consistent across
every mode, on top of either the rainbow or the corner-accent
ambient layer.
**2. Drop the global `environments` / `defaultEnvironment` config**
Per-instance env knowledge now lives entirely in the **Site host
mappings** section. There's no longer a parallel "Environments" config
that duplicated the same hostnames in a different shape.
- Removed `Environment`, `EnvironmentHosts`, `EnvironmentConfig`,
`ENVIRONMENT_ORDER`, `ENVIRONMENT_LABELS`,
`DEFAULT_ENVIRONMENT_HOSTS` from `lib/types`.
- Removed `environments` and `defaultEnvironment` from
`UserPreferences` and `DEFAULT_PREFERENCES`.
- Deleted `EnvironmentSwitcher` component and its `.cs-env-pill`
styles.
- Removed `useEnvHost()` from the panel store. All callers now pass
no host override, so `buildUrl` etc. fall back to the URI's embedded
host (which is the page's host) β the right default for same-page
operations.
- `DiffView` rewired: the `Compare:` dropdown now derives its
cross-env options from `findMappingForHost(location.hostname,
siteHosts)` β one option per other env in that mapping that has a
host configured. The right-side fetch uses the mapping's host for
the chosen env. When the current host isn't in any mapping, only
Published-vs-Draft is offered (with an inline help nudge to add
the mapping).
- `Options` page: dropped the **Environments** section. Updated the
**Site host mappings** copy to call out that it's now the single
source of truth for cross-env behavior.
**Test + doc updates**
- `tests/content/highlighter.test.ts`: replaced the corner-tick
contract block with a rainbow-contract block (six per-color
outline rules per active mode, reveal-gating contract, hover/selected
exclusion, editable-stays-corner-tick contract). Added an
`applyHighlights` test for the cycling color index.
- `README` + `PRIVACY`: updated highlight-mode description, dropped
the "Environment switcher" bullet, rewrote the **Site host
mappings** section to call out the Diff-tab integration.
- Bumped to `2.2.0` (breaking pref-shape change for forks reading
`preferences.environments`; user-stored values are silently
ignored β they're inert leftovers in `chrome.storage.sync` that
no code reads).
Co-authored-by: Cursor <cursoragent@cursor.com>
Pivot the docs to reflect the actual distribution channel: the .zip attached to each GitHub Release. There's no Chrome Web Store listing to point people at, so the install instructions need to be the first thing a non-contributor sees and they need to walk through "Load unpacked" step by step. README: - New top-level **Install** section. First-time install (download zip β unzip to a stable folder β enable Developer mode β Load unpacked β pin), update flow (download new zip β unzip over the old folder β β» Reload), and removal flow. Includes a "Don't move/delete the folder" warning (a common foot-gun with sideloaded extensions), the Chromium-browser variants of the extensions URL (Chrome / Edge / Brave / Arc / Vivaldi / Opera), the "Developer mode" yellow-banner reassurance, and a Troubleshooting table for the five most common symptoms. - Demoted the previous "Install (development)" section to **Build from source** and moved it down past Configuration. Most readers shouldn't need it. - Stripped the **"Upload the zip to the Chrome Web Store dashboard"** step from the Releasing section. Publishing the GitHub draft IS the release; users grab the zip from there. - Updated the Privacy summary line to drop the CWS reference. - Tweaked the Architecture sidebar (env hosts β site host mappings) and bumped the test count in the Migration notes from 84 β 162. PRIVACY: - "Linked from the Chrome Web Store listing" β "Distributed alongside the GitHub releases β the only place Clay Slip is published". - Remote-code section now points at GitHub Releases for the .zip and notes that anyone can verify by checking out the matching tag and running the build. - Storage-table preferences row updated to drop "environment hosts" (we removed that config) and add highlight mode/intensity. Release workflow + zip script: - Comments updated to call out that GitHub Releases is the primary destination (CWS layout is mentioned only as the secondary "if we ever publish there" reason for keeping manifest.json at the zip root). - Zip script's "Next steps" now prints a sideload smoke-test recipe (unzip β enable Developer mode β Load unpacked) and a pointer at the README β Releasing flow for publishing, instead of the dead "upload to the CWS dashboard" instructions. No code changes β `npm run validate` passes (162/162). Smoke-tested `npm run release:dry` end-to-end, zip layout verified. Co-authored-by: Cursor <cursoragent@cursor.com>
Switch the `?edit=true` behavior from "no-op the entire extension" to "go passive": the panel still mounts and every read-only feature stays fully usable, but we don't paint outlines on the host page or capture clicks/hovers there. Clay's own in-page editor chrome owns the host DOM β we stay out of its way without sacrificing the panel's inspection/diff/SEO/notes/copy features. **Mechanism: gate every host-element write on stylesheet presence** Added `isHighlighterInstalled()` (presence of the `<style>` tag is the source of truth) and gated every element-writing helper on it: applyHighlights, clearHighlights, setSelected, setHovered, setAnnotatedUris, setFindMatches. The global `<html>` writes (setHighlightMode/Opacity/Reveal) are NOT gated since they're low-impact and useful for keeping prefs in sync. This means we don't have to plumb an `editMode` flag into every call site β bootstrap simply skips `installHighlightStyles()` in edit mode and every downstream helper silently no-ops. Symmetric: any future "unmount panel" path that removes the style tag would also flip the helpers back to no-op. **Bootstrap behavior** - ALL Clay pages now send `CLAY_DETECTED` (including edit-mode ones) so the toolbar popup shows the panel-toggle UI on edit pages too. - On edit-mode pages: `syncComponents()` (read-only) β mount panel β badge. Skipped: `installHighlightStyles`, `applyHighlights`, `installAltRevealListener`. - On normal pages: same as before β full `paintAndSync` + alt-reveal + deep-link auto-select. - `PANEL_TOGGLE` handler refactored: re-mounting on an edit page also stays in passive mode. **Panel-side** - `useElementSelection` bails early in edit mode β no document-level click/mouseover listeners installed. Tree-click selection still works because it goes through the store directly. - `ComponentDetails` empty state shows a contextual hint on edit-mode pages: "Page is in Clay edit mode β pick a component from the Tree tab to inspect it." instead of the misleading "click any component on the page" copy. **Tests** - 5 new tests for the no-op-until-installed contract: applyHighlights, setSelected/setHovered, setAnnotatedUris, clearHighlights, and an install-then-uninstall round-trip (locking in the symmetry). - Per-describe `beforeEach(installHighlightStyles)` added to the active-mode tests that were previously implicitly relying on the helpers writing without install. - 167/167 tests pass (was 162; added 5). **Docs** - README "Disabled in Clay edit mode" β "Passive in Clay edit mode" bullet rewritten with the full list of what does/doesn't work. - README troubleshooting row swapped: instead of "Floating button doesn't appear" (no longer true β it appears), now "Clicking on the page doesn't select any component β use the Tree tab". - `clay-uri.ts` `isEditMode` doc comment updated to describe the passive-mode contract. Co-authored-by: Cursor <cursoragent@cursor.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
Three rounds of polish that together rewire how the extension renders highlights, how it learns about cross-environment hostnames, and how it behaves on Clay edit-mode pages.
1. Highlight modes β final shape
[data-editable]onlyThe rainbow ambient layer in
alland the β₯-peek matches the look the original Clay devtools shipped with β easier to read at a glance as a structural map of the page than the corner accents we tried in the interim. Hover and selected always paint in the single blue accent on top of either ambient layer, so the click-feedback signal stays consistent across every mode.2. Drop the global
environments/defaultEnvironmentconfigPer-instance env knowledge now lives entirely in the Site host mappings section of the options page β there's no longer a parallel "Environments" config that duplicated the same hostnames in a different shape. The Site host mappings already hold per-brand hostnames for prod / staging / qa, which is everything the panel needs:
Compare:dropdown derives its cross-env options from it:findMappingForHost(location.hostname)β list every other env in that mapping that has a host configured β one option per env. The right-side fetch uses the mapping's host for the chosen env. When the current host isn't in any mapping, only Published-vs-Draft is offered (with an inline help nudge).useEnvHost()is gone. All callers now pass no host override, sobuildUrletc. fall back to the URI's embedded host β the right default for same-page operations. TheEnvironmentSwitcherpill is gone too.3. Passive mode on
?edit=truepagesThe previous "no-op the entire extension on edit pages" behavior was too aggressive β users lost the panel, the tree, the JSON tab, the diff, the SEO tab, every copy button. Reworked so:
Mechanism: every host-element-writing function in
highlighter.tsnow no-ops untilinstallHighlightStyles()has been called. The presence of the<style id="clay-slip-highlight-styles">tag is the source of truth (isHighlighterInstalled()). On edit-mode pages we simply skip the install call and every downstream helper silently no-ops β no per-call-siteeditModeflag plumbing required. The empty state in the Inspect tab adapts on edit pages: "Page is in Clay edit mode β pick a component from the Tree tab to inspect it."Files touched
src/content/highlighter.tsβisHighlighterInstalled()gate, every element-writing helper checks it (applyHighlights,clearHighlights,setSelected,setHovered,setAnnotatedUris,setFindMatches);PALETTEconstant +data-clay-slip-color-idxattribute drive the rainbow; six per-color CSS rules per active mode;editablekeeps corner tickssrc/content/index.tsβ split intosyncComponents()(read-only) +paintAndSync()(full); bootstrap branches onisEditMode();PANEL_TOGGLEhandler also keeps passive mode on re-mountsrc/content/panel/hooks/useElementSelection.tsβ early-return in edit mode; no document-level click/mouseover listeners attachedsrc/content/panel/components/ComponentDetails.tsxβ empty-state copy adapts in edit modesrc/lib/clay-uri.tsβisEditMode()doc comment rewritten to describe the passive-mode contractsrc/lib/types.tsβ droppedEnvironment,EnvironmentHosts,EnvironmentConfig,ENVIRONMENT_*,DEFAULT_ENVIRONMENT_HOSTS, plusenvironments/defaultEnvironmentfromUserPreferencessrc/options/Options.tsxβ Environments section gone; Site host mappings copy updatedsrc/content/panel/store.tsβuseEnvHost()removedsrc/content/panel/components/DiffView.tsxβ env-compare options derive fromsiteHostssrc/content/panel/components/EnvironmentSwitcher.tsxβ deletedsrc/content/panel/components/{PageInfo,JsonPreview,ComponentDetails}.tsx+hooks/useKeyboardShortcuts.tsβ dropuseEnvHost, pass no host overridesrc/content/panel/App.tsxβ drop EnvironmentSwitcher mountsrc/content/panel/styles.cssβ drop.cs-env-pillrulestests/content/highlighter.test.tsβ 5 new passive-mode tests, rainbow contract tests, color-index cycling test, per-describebeforeEach(installHighlightStyles)for active-mode testsREADME.md+PRIVACY.mdβ updated for the new mode shapes, the dropped env switcher, and the passive edit-mode behaviorpackage.jsonβ2.1.0β2.2.0Test plan
npm run typechecknpm run lintnpm run format:checknpm test -- --run(167 / 167 passing)npm run buildβ clean builddist/in Chrome on a Clay page (non-edit):selectionand the page is pristineallmode β rainbow always-on; hover/click still blue on topeditablemode β only[data-editable]shows subtle corner accentsoffβ nothing painted?edit=true:Compare:dropdown lists each other env in that mappingchrome://extensionsβ Clay Slip β Options β confirm there is no "Environments" section, only "Site host mappings"Compatibility note
environmentsanddefaultEnvironmentalready-stored inchrome.storage.syncare silently ignored β they're inert leftovers that no code reads. No migration is required.