Skip to content

fix(theme): emit the fixed-theme value with tojson, not a hand-quoted string - #4

Merged
Sunrisepeak merged 1 commit into
mainfrom
fix/script-escaping
Aug 13, 2026
Merged

fix(theme): emit the fixed-theme value with tojson, not a hand-quoted string#4
Sunrisepeak merged 1 commit into
mainfrom
fix/script-escaping

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

The bug

Autoescaping applies inside <script> too. The pre-paint bootstrap built its
value by hand as '"' + config.style + '"', so a consumer index setting
theme.style: "dark" got this on every page:

var fixed = &#34;dark&#34;;

A classic script is not HTML-parsed, so those entities never decode. It is a
SyntaxError — and a syntax error takes down the entire script element,
not just that line.

Why it is worse than a broken theme

That IIFE does two unrelated things: the pre-paint theme, and the language
redirect. So setting a theming option silently disabled i18n on every
page of the site.

Measured on a consumer index (Sunrisepeak/dsh-index) with style: "dark":

document.documentElement.dataset.theme  ->  "(unset)"
localStorage 'xpi-theme'                ->  null
locale redirect                          ->  never fired

After switching that index back to "auto" (var fixed = null, which parses),
both the theme and the redirect started working again — the redirect landing on
/zh/ was how I confirmed it had been dead.

This is also why it went unnoticed: "auto" is the default and renders null,
so only an index that explicitly pins a theme hits it. xim-pkgindex uses
"auto".

The fix

tojson instead of a hand-quoted string. It emits "dark" / "light" /
null and escapes for a script context, so it fixes the instance and the class.

Test

tests/test_script_escaping.py asserts on the class, not the string:

  • scans every inline script in the whole generated site for HTML entities
    that are meaningful in JS (&#34; &quot; &#39; &apos; &amp; &lt; &gt;),
    across style = auto / dark / light;
  • parses the emitted value with json.loads rather than comparing text, so a
    future change that produces valid-but-wrong JS still fails.

Verified it catches the bug: reverting the template makes 4 of its 6 cases
fail
; with the fix, 6 pass.

Full suite: 42 passed.

I also scanned every other {{ }} interpolation inside a <script> in the
templates — this was the only one.

… string

Autoescaping applies inside <script> too, so building the value as
`'"' + config.style + '"'` reached the page as

    var fixed = &#34;dark&#34;;

A classic script is not HTML-parsed, so those entities never decode: it is a
SyntaxError, and a syntax error takes down the whole element. The visible
symptom was just "theme.style: dark does nothing", but the same IIFE also
carries the pre-paint language redirect, so setting a theming option silently
disabled i18n on every page. Measured on a consumer index:
document.documentElement.dataset.theme came back "(unset)" and no locale
redirect fired.

`tojson` emits "dark" / "light" / null and escapes for a script context, so
it fixes the instance and the class.

The test asserts on the class rather than the string: it scans every inline
script in the generated site for HTML entities that are meaningful in JS, and
parses the emitted value with json.loads instead of comparing text. Reverting
the template makes 4 of its 6 cases fail.
@Sunrisepeak
Sunrisepeak merged commit c949d84 into main Aug 13, 2026
5 checks passed
Sunrisepeak added a commit to Sunrisepeak/dsh-index that referenced this pull request Aug 13, 2026
1. Install command drops pnpm. Plugins already declare xim:pnpm in their own
   deps via template.lua, so it pulls in when a plugin is installed; making
   every reader type it to install dsh itself was noise.
2. Forum and docs icons removed from the header.
3. About now points at openxlings/xlings rather than this index.
4. The license axis is gone from the facet row. License stays on the package
   page -- it is what gates mirroring -- but nobody browses by SPDX id.
5. Categories and keywords are now real per-package values, surfaced as the
   two new facet axes.

Categories come from tools/classify.py, which reads three author-written
signals: the plugin name, the repo's GitHub topics, and the repo description.
Bundle patches were evaluated as a fourth and rejected -- almost every one just
inserts the plugin by name, so it carries no taxonomy. Topics alone covered
only 74/169 and names alone 94/169; adding descriptions (matched bilingually,
since most of this ecosystem writes Chinese) reaches 133/169.

The remaining 36 stay 'uncategorized' rather than being pushed into a bucket
that would look authoritative. A facet whose counts are honest is worth more
than one that is merely full.

theme.style is back to 'dark' now that openxlings/xpkgindex#4 is merged and
the value reaches the page as valid JS.
Sunrisepeak added a commit to Sunrisepeak/dsh-index that referenced this pull request Aug 13, 2026
Bootstraps the DeepSeek Harness plugin index: 168 data-only descriptors plus
one shared template.lua, appended at index-build time by pkgindex-build.lua.

The upstream install paradigm is uniform enough to justify templating it --
169 of 281 repos on the dsh-plugin topic declare dsh.bundle, and 166 of those
use the same ./cordis.patch.yml path -- so writing the four lifecycle hooks
169 times would be 169 copies that rot independently.

One template covers both delivery architectures behind a single branch on
package.dsh.mirror. Mirrored packages carry a real xpm resource with a sha256
and, once the GitCode release is confirmed, a CN URL; un-mirrored ones carry {}
and pnpm fetches from the pinned upstream commit. Which one a package gets is
decided by its license, because mirroring is redistribution: of the 169 bundles
surveyed, 29 ship no LICENSE and 13 are unclassifiable, and this index has no
right to mirror those. The site labels every package accordingly rather than
implying a guarantee it cannot make.

Also ships tools/mirror.py and the first real mirror (dsh-cc-tui-0.1.6),
published to xlings-res/dsh-plugins on both GitHub and GitCode and verified
three ways -- the bytes built here, what GitHub serves and what GitCode serves
all hash to b8016904a72e7bf4858a0bb24e6eb952357ecdc7e963a482f33e143ea2312d1b.

Three defects were found by the checks rather than by review:

- 19 of 169 descriptors had a version key that did not match package.json at
  their own pinned commit, because version and sha came from two surveys taken
  minutes apart. gen_descriptors.py now reads both from the pinned sha, making
  the mismatch unrepresentable rather than merely detected.
- The card strip printed `dsh plugin add <bundle_name>`, a command that fails:
  package names are not resolvable identifiers in this ecosystem.
- theme.style "dark" emitted invalid JS, which killed the language redirect
  sharing that script. Fixed upstream in openxlings/xpkgindex#4.

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
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.

1 participant