Add data themes: shareable YAML progress themes#241
Conversation
|
Warning Review limit reached
More reviews will be available in 39 minutes and 4 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A data theme is an inert YAML file — scalars, arrays and hashes only,
loaded with YAML.safe_load_file — so themes can be shared and used
without reading them. This is the trust story Liquid was supposed to
provide but could not: taski themes shipped as Ruby classes, putting
the envelope outside the sandbox. Data themes move the boundary to the
file format itself: no code runs.
Templates are %{placeholder} format strings whose names ARE the
Theme::Base helper vocabulary (%{duration_part}, %{icon}, %{spinner},
%{task_names_part}, ...) — one vocabulary across Ruby themes and data
themes, computed by the same helper methods. Icons, colors (truecolor
works: YAML double-quoted "\e[38;2;...m" parses to ESC), spinner
frames/intervals, count/duration formats, truncation knobs, and the
*_part fragment wrapper strings are all data; the only-when-present
guards stay engine-fixed. Anything undeclared falls back to the
`extends` theme (base/default/plain/detail/compact — a closed enum, no
constant lookup from file input).
Theme.load(path) / Theme.from_hash(hash) validate everything fail-fast
and bake a frozen Definition into an anonymous Theme::Declarative
subclass — IS-A Theme::Base with zero-arg .new, so Config's existing
theme contract is untouched. Config#theme= additionally accepts a
String/Pathname (raising Theme::LoadError at assignment, never
mid-run). Validation closes every hole found in design review: closed
key sets at every level (unknown keys name the allowed set), schema
versioning (schema: 1), type checks, a stray-% check (strip
/%%|%\{[^}]*\}/ then reject any remaining % — %s/%d would otherwise
silently inline the whole payload hash via Kernel#format), and a
validation render of every declared template/fragment against a fully
populated sample payload, which is exhaustive because the render
payload is uniform. A theme that loads cannot fail at render time; if
anything slips through anyway it hits the existing render_theme
isolation.
Parity proof: the four shipped themes re-encoded as YAML
(test/fixtures/themes/*.yml) reproduce all 132 cases of the Liquid-era
byte-parity baseline through the data-theme pipeline.
Also ships examples/themes/catppuccin-mocha.yml (truecolor) and the
first user-facing theme documentation (GUIDE "Customizing Themes":
Ruby-theme contract + helper API + full placeholder table; README
teaser).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2e550eb to
1161195
Compare
Summary
Stacked on #235. Adds data themes: progress-display customization from an inert YAML file — scalars, arrays and hashes only (
YAML.safe_load_file), so no code runs and themes can be shared and used without reading them. This is the trust story Liquid was supposed to provide but couldn't (taski themes shipped as Ruby classes, putting the envelope outside the sandbox); data themes move the boundary into the file format itself.Design
Theme::Basehelper names (%{duration_part},%{icon},%{spinner},%{task_names_part}, ...) and are computed by the same helper methods. Docs, tests and mental model are shared between Ruby themes and data themes.Theme.loadbuilds an anonymous class that actually inherits from theextendstheme (base/default/plain/detail/compact— a closed enum, no constant lookup from file input) with the data layer on top. Declared templates render from format strings; everything undeclared falls through (super) to the extends theme on the same instance — so declared colors/icons/spinner/formats/fragments apply to fallback-rendered lines too, exactly like a Ruby subclass. A colors-only theme restyles every line.Theme::Basewith zero-arg.new;Config#theme=additionally accepts String/Pathname.Fail-fast validation — a theme that loads cannot fail at render time
schema: 1versioning, checked before anything else (a future-schema file says "unsupported schema version", not "unknown key").infwould kill the spinner/render threads withRangeErrorfromsleep; tiny values busy-loop the CPU); truncation integers must be positive%rejection: strip/%%|%\{[^}]*\}/, reject any remaining%—%s/%dwould otherwise silently inline the whole payload hash viaKernel#formatTheme::LoadErrornaming the fileformatonly as argument values, never as format strings — no injection surface (adversarially verified)Parity proof
The four shipped themes re-encoded as YAML (
test/fixtures/themes/*.yml) reproduce all 132 cases of the Liquid-era byte-parity baseline through the data-theme pipeline.Also ships
examples/themes/catppuccin-mocha.yml(truecolor)rake standardcleanAdversarial review
A 39-agent adversarial review (validation bypass / parity-fallback / security / mutation testing / integration) ran before this PR. Security probes confirmed no code-execution path and no format-string injection. 24 confirmed findings (heavily overlapping across angles) were folded in:
clean_*/group_*fallback lines).rescue Psych::Exception(aliases/date scalars no longer escape as raw Psych errors through the documentedLoadErrorcontract).inf/1e20/1e-4rejected at load — previously killed the render thread and abortedon_stopteardown, leaving the cursor hidden)from_hashinput now normalized (previously{schema: 99}bypassed the schema gate silently)schemamust be exactly 1; checked before unknown-key validationtruncation.list_limit/text_max; deep-frozen definition strings; README example path fixed;format_duration1000ms-boundary mutant killed🤖 Generated with Claude Code
(Recreated from #236, which GitHub auto-closed when its base branch
feat/ruby-native-themeswas deleted on merge of #235. Rebased onto master; gates green 822 runs / 0 failures.)