Skip to content

Selector Maintenance

Amir edited this page Sep 1, 2026 · 2 revisions

Language: English · فارسی

Selector maintenance

Bale Web is a React app bundled with CSS Modules: every class name in the DOM is a per-build hash like _BKsFW, so it changes on every deploy. The extension anchors on attributes that survive a rebuild instead. When one of those is renamed upstream, a category stops blurring — and this page is how it gets fixed.

Reference version in the repo: docs/SELECTORS.md.

The anchors, in order of trust

1. aria-label — hand-written for accessibility, semantic, rarely churned.

Value Marks
dialog-item one row in the sidebar chat list
message-item one message row
avatar any avatar container
member-dialog one person in the profile / members panel
ChatAppBar the header above the open conversation
editable-message-text the composer
count-badge-text unread counter — never blurred

2. data-sentry-source-file / data-sentry-component — emitted by Sentry's component-annotate plugin, derived from source names: Dialog.tsx, BaseBubble.tsx, Text.tsx, Preview.tsx, Thumbnail.tsx, SmallDialog.tsx, NewTextContainerFC.

3. data-testidphoto-message, thumbnail, story-item.

Shapes the registry depends on

<!-- sidebar row -->
<div aria-label="dialog-item" data-sentry-source-file="Dialog.tsx">
  <div aria-label="avatar"></div>
  <div>
    <div><div><bdi>chat name</bdi></div><span>12:04</span></div>
    <div title=""><div><div dir="rtl"><span>last message</span></div></div></div>
  </div>
</div>

<!-- message -->
<div aria-label="message-item" data-sid="client-message:…">
  <div aria-label="avatar"></div>
  <div data-sentry-source-file="BaseBubble.tsx">
    <p><span>sender name</span></p>          <!-- groups only -->
    <div data-sentry-source-file="Preview.tsx"></div>
    <div data-sentry-source-file="Text.tsx"></div>
    <div data-sentry-source-file="Info.tsx"><p>12:05</p></div>
  </div>
</div>

Two details worth remembering:

  • The sender name is the only p wrapping a span; the timestamp p holds its text directly. That is what keeps timestamps readable.
  • Message text is blurred at the container, not at leaf elements, because a bubble legitimately contains inline markup (<strong>, links) and a leaf rule leaves the surrounding text sharp.

Finding a replacement

Fastest: diagnostics

Options page → Advanced selectorsLog selector diagnostics, reload web.bale.ai, read the console table. Rows with 0 matches are dead.

Then: the console probe

Paste tools/dom-probe.js into DevTools on a Bale tab:

bp.status()      // dead vs matching selectors, read from the injected stylesheet
bp.inventory()   // every attribute the page renders, with value counts
bp.el($0)        // ancestors + subtree of the element you inspected
copy(bp.last)

bp.inventory() is usually the whole answer: it shows which attribute families still exist and what their values are now.

For contributors: the driven browser

npm run build
npm run browser                                   # log in once, window stays open
node scripts/browser.mjs eval tools/leak-scan.js  # what is still readable
node scripts/browser.mjs shot out.png
node scripts/browser.mjs hover '[data-sentry-source-file="Text.tsx"]'

The profile persists in .browser-profile/, so you log in once and iterate.

Writing the selector

Add it to the right entry in src/content/engine/selectors.ts. Picture-only selectors go under mediaSelectors, which get the stronger graphic blur under the same user-facing switch.

Two traps that have already bitten this project:

  • Keep :has() last in a compound. *:not(:has(*)):not(:empty) is valid CSS but some engines — including the one jsdom uses, and therefore the test suite — reject it.
  • Put a type selector before an attribute inside :has(> …). Write :has(> div[data-sentry-component="X"]), not :has(> [data-sentry-component="X"]), for the same reason.

npm test parses every shipped selector and checks the targeting rules against a fixture transcribed from the real markup, so both traps fail loudly.

As a user, without waiting for a release

Options page → Advanced selectors, one selector per line under the matching category. Braces, <, ;, @, backslashes, comment markers and commas are rejected; at most 20 selectors per category, 200 characters each.

Clone this wiki locally