Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
6e8489f
fix(text): embed each bundled font once
jacquetc Oct 16, 2026
9842d0f
docs(engineering): specify the KDE colour model and the Breeze metrics
jacquetc Oct 16, 2026
1717119
refactor(styles): generate the style-slot roster, Debug and PartialEq
jacquetc Oct 16, 2026
2dfdbd1
feat(styles): one state-aware paint projection hook
jacquetc Oct 17, 2026
5859664
feat(styles): accept a negative focus-ring offset
jacquetc Oct 17, 2026
c4cb5d8
feat(styles): render the StandardItemRecipe row heights
jacquetc Oct 17, 2026
400d15b
feat(styles): render the TableRecipe row and header heights
jacquetc Oct 17, 2026
b12d4c6
feat(styles): render the TabRecipe tab height and header padding
jacquetc Oct 17, 2026
26aed9e
feat(styles): render the ScrollBarRecipe minimum thumb length
jacquetc Oct 17, 2026
344a0e5
feat(styles): add selection-emphasis and pressed-label style hooks
jacquetc Oct 17, 2026
2c14876
feat(tokens): carry view and semantic colours in OsThemeColors
jacquetc Oct 17, 2026
3cad640
feat(kde-scheme): add the KDE configuration and colour-scheme reader
jacquetc Oct 17, 2026
091a5a1
feat(kde-scheme): add KDE's colour arithmetic and the clean-room guard
jacquetc Oct 17, 2026
fa3c679
feat(kde-scheme): resolve a scheme into its palette and report it
jacquetc Oct 17, 2026
9d133c9
feat(kde-scheme): apply an accent colour to a scheme as Plasma does
jacquetc Oct 17, 2026
0a36061
feat(platform): read the desktop portal and AT-SPI flag over zbus
jacquetc Oct 17, 2026
4333763
feat(platform): read the KDE desktop through teksilo-kde-scheme
jacquetc Oct 17, 2026
0e9c28c
refactor(app): resolve every Native-mode theme through one function
jacquetc Oct 17, 2026
b99a91b
fix(app): keep the density and the hint in themes that follow the OS
jacquetc Oct 17, 2026
534e0e2
feat(platform): follow live desktop appearance changes with OsThemeWatch
jacquetc Oct 17, 2026
4704f2c
feat(theme-kde): add the KDE Plasma preset's tokens, palette and accents
jacquetc Oct 17, 2026
7821b20
feat(theme-kde): paint Button, IconButton and SplitButton as Breeze does
jacquetc Oct 17, 2026
b178e36
feat(theme-kde): paint the switch, check box, radio and field as Bree…
jacquetc Oct 17, 2026
7e360cb
feat(theme-kde): paint the combo box, spin box and slider as Breeze does
jacquetc Oct 17, 2026
49a8dbb
feat(theme-kde): paint menus, popovers and tooltips as Breeze does
jacquetc Oct 17, 2026
8adb67d
feat(theme-kde): paint rows, tables, tabs, scroll bars and progress b…
jacquetc Oct 17, 2026
3ad9ab1
feat(theme-kde): build the recipe slots with Breeze's metrics
jacquetc Oct 17, 2026
9e96e25
feat(theme-kde): build the theme of the live KDE desktop
jacquetc Oct 17, 2026
33bbb2e
feat(theme-kde): name or embed Plasma's Noto Sans and Hack
jacquetc Oct 17, 2026
1a34e27
ci(theme-kde): guard the KDE crates' packages and build their font fe…
jacquetc Oct 17, 2026
e24487b
test(theme-kde): compare the Breeze controls' pixels with Plasma's
jacquetc Oct 17, 2026
eac8d46
test(theme-kde): measure Breeze in the target-conformance gate
jacquetc Oct 17, 2026
74a35fa
fix(tokens): lift each theme's own colours for high contrast
jacquetc Oct 17, 2026
16f5389
fix(theme): mirror the macOS and Fluent sliders under right to left
jacquetc Oct 17, 2026
654e59a
fix(theme): size the macOS and Fluent menus for the active density
jacquetc Oct 17, 2026
e6414ee
fix(theme): run the macOS and Fluent vertical sliders from the bottom
jacquetc Oct 17, 2026
09e8a6d
feat(teksilo): add the theme-kde feature and offer KDE in the widget …
jacquetc Oct 17, 2026
607465e
docs(theme-kde): document the KDE preset and Native mode, with every …
jacquetc Oct 18, 2026
6c41576
docs(theme-kde): name KDE in the preset lists and counts that predate it
jacquetc Oct 18, 2026
2c18532
fix(kde-scheme): read the scheme file under a file-immutable kdeglobals
jacquetc Oct 18, 2026
e432106
fix(kde-scheme): keep the KDE words forbidden on a doc.qt.io line, ac…
jacquetc Oct 18, 2026
bca5c15
fix(os-theme): post the window a closed bus leaves open, and connect …
jacquetc Oct 18, 2026
6ec461d
fix(theme-mode): open a FollowSystem window on the desktop's preferen…
jacquetc Oct 18, 2026
a904faa
fix(theme): reach the high-contrast and view-colour floors on every p…
jacquetc Oct 18, 2026
8ddb4cc
fix(theme-kde): keep every lifted colour readable for any scheme, ina…
jacquetc Oct 18, 2026
b8f7540
fix(theme-kde): frame the editable rich-text editor as the line edit,…
jacquetc Oct 18, 2026
d05a322
fix(kde-scheme): name both test targets that read the vendored Breeze…
jacquetc Oct 18, 2026
16884b5
fix(theme-kde): paint button, header and check-box faces on the surfa…
jacquetc Oct 18, 2026
1260aa4
fix(theme-kde): lift the secondary text on an unselected tab's faces
jacquetc Oct 18, 2026
ea832da
fix(theme-kde): paint a check box over a translucent button colour as…
jacquetc Oct 18, 2026
cbad5ef
test(theme-kde): measure the Breeze preset in the running widget catalog
jacquetc Oct 18, 2026
6649803
test(theme-kde): measure the visual check on a configuration of its own
jacquetc Oct 18, 2026
89af9de
docs(theme-kde): state why a one-line tooltip is shorter than Plasma's
jacquetc Oct 18, 2026
415e71b
feat(cargo-teksilo): add `update`, and name outdated companion binari…
jacquetc Oct 18, 2026
2e16b19
docs(changelog): rewrite the Unreleased section for its readers
jacquetc Oct 18, 2026
c5d50d2
docs(changelog): restore five facts the condensed Unreleased section …
jacquetc Oct 18, 2026
63f4117
Merge branch 'dev' into feat/theme-kde
jacquetc Oct 18, 2026
da41f8d
fix(cargo-teksilo): join the checkout's install path with the platfor…
jacquetc Oct 18, 2026
bda6257
fix(kde-scheme): join the defaulted XDG homes with a slash on every host
jacquetc Oct 18, 2026
ce918d3
test(platform): run the KDE config-file watch test on Unix only
jacquetc Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions .claude/CLAUDE.md

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions .claude/CLAUDE.review.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ These are the explicit rules the codebase is meant to follow. Flag deviations.
- **Error types:** `thiserror` (`#[derive(thiserror::Error)]` with `#[error("...")]` per variant;
`#[from]` for transparent conversions, `#[source]` for nested chains). Do not hand-roll
`Display` / `std::error::Error` / `From`.
- **Clean-room KDE code:** `teksilo-kde-scheme` is written from
`engineering/docs/kde-breeze-spec.md` only, never from KDE or Qt source.
`crates/teksilo-kde-scheme/tests/clean_room_vocabulary.rs` fails on upstream identifiers in
the scanned directories; flag a new KDE crate missing from its `SCANNED` list, a word removed
from its list, and code whose structure mirrors upstream rather than the specification.

---

Expand Down Expand Up @@ -128,8 +133,10 @@ teksilo-preview-ui Reusable 3-pane previewer GUI
teksilo-widgets-previewer Bundle binary for the stock catalog
teksilo-terminal Embeddable terminal-emulator widget; PTY + VT delegated behind a
TerminalEngine trait. Renders into the wgpu surface (unlike webview)
teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos
teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos / teksilo-theme-kde
Sibling design-language presets (tokens + Tier-3 chrome), feature-gated
teksilo-kde-scheme KDE Plasma colour-scheme model and desktop settings; no I/O (environment and
files passed in); read by teksilo-platform and teksilo-theme-kde
teksilo-inspector In-app debug introspection panel; cfg(debug_assertions) only
teksilo-automation GUI-free automation toolkit (DTOs, execute(), tool catalog, wire protocol)
teksilo-automation-mcp MCP server binary driving it
Expand All @@ -143,7 +150,7 @@ Stated dependency flow (verify):
`canvas → render → platform → app`, `settings → app`, `i18n-macros → i18n`,
`core → preview`, `preview-ui → preview + widgets`, `widgets → scene`, `core → webview`,
`(app + core) → async → {tokio, async-std}`, `(core + tokens + canvas + platform) → terminal`,
`(core + canvas) → automation → automation-mcp`.
`(core + canvas) → automation → automation-mcp`, `tokens → kde-scheme → {platform, theme-kde}`.

---

Expand Down Expand Up @@ -196,7 +203,11 @@ axis at each child's final main size, then places. All in logical pixels. `Leadi
`ThemeAppearance::{Light, Dark}` is a required field. Presets `presets::intui::{light, dark}` ship
in `teksilo-core`. There is no `Theme::default()`. Per-widget style traits live in
`teksilo-core/src/styles/`; default `Recipe*Style` impls in `teksilo-widgets/src/styles/`. Apps
install styles per-call (`.style(...)`) or theme-wide (`theme.style_slots.<widget> = ...`).
install styles per-call (`.style(...)`) or theme-wide (`theme.style_slots.<widget> = ...`). The
sibling presets (Material 3, Fluent, macOS, KDE's Breeze) are separate crates behind umbrella
features. `ThemeMode::Native` adopts the live desktop's colours through one function,
`resolve_native_theme`; a theme change after a window is built re-tints and keeps the shapes the
tree was built with, by design (`docs/native-theme.md`).

### App entry point

Expand Down Expand Up @@ -271,6 +282,7 @@ Core:
- Accessibility: `crates/teksilo-core/src/accessibility.rs`
- Animation: `crates/teksilo-core/src/animation.rs`, `animated_quad.rs`, `frame_tick_scheduler.rs`, `motion_visibility.rs`
- Theme/styling: `crates/teksilo-core/src/styles/` (+ preset `presets/intui.rs`); default impls `crates/teksilo-widgets/src/styles/`
- Native mode: `crates/teksilo-platform/src/os_theme.rs` (+ `os_theme/kde.rs`, `os_theme/watch.rs`), `crates/teksilo-app/src/native_theme.rs`
- Actions/Intents/Shortcuts: `crates/teksilo-core/src/{action,intent,shortcut}.rs`; `IntentKind` derive in `crates/teksilo-macros/src/intent_kind.rs`
- Drag-and-drop core: `crates/teksilo-core/src/drag_payload.rs`, `drag_state.rs`, `widget_tree/drag_drop_impl.rs`

Expand Down
24 changes: 19 additions & 5 deletions .claude/skills/teksilo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ thread, persistence, file dialogs, the clipboard, notifications, menus, a
terminal, a web view), run `cargo teksilo search "<what you need>"`. Several
subsystems are off by default and invisible until you look: the async executor
and its Tokio / async-std adapters (`teksilo-tokio`, `teksilo-async-std`),
`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS
`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS / KDE
theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates.

## Sources of truth — when they disagree, trust in THIS order
Expand All @@ -55,10 +55,11 @@ theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates.
If the version this app resolves (`cargo teksilo status`, or `Cargo.lock`) is
not the one this copy ships with, the skill was installed for another release.
Say so, and refresh it before relying on the prose: `cargo install
cargo-teksilo --version <the app's teksilo version>`, then `cargo teksilo agent
install claude --force` (add `--user` for a copy under the home directory).
`cargo teksilo status` flags a user-scope copy that differs from the installed
tool's.
cargo-teksilo --version <the app's teksilo version> --locked`, then `cargo
teksilo update` (`--user` for a copy under the home directory). `cargo teksilo
status` flags a tool older than the app, a user-scope copy that differs from the
installed tool's, and an automation server or formatter on PATH from an older
release, each with the command that fixes it.

## The tool: `cargo teksilo`

Expand Down Expand Up @@ -96,6 +97,7 @@ cargo teksilo show docs/scroll-area.md # read a search result in full, offl
cargo teksilo show docs/scroll-area.md --lines 166-172
cargo teksilo probe install # materialise the Python automation harness
cargo teksilo init # the harness, plus a teksilo brief for every agent here
cargo teksilo update # bring everything installed to this release
```

- **`symbol`** replaces reading the widget source by hand. It emits the type's
Expand Down Expand Up @@ -132,6 +134,18 @@ cargo teksilo init # the harness, plus a teksilo brief f
`--user` supports `claude`, `vibe`, and `opencode`; it writes no project harness.
`--force` replaces conflicting generated files. Shared-file text outside the
managed region is preserved. `agent list` reports supported targets and state.
- **`update`** brings what is already installed to this release: the harness,
the project's agent instructions, and the `teksilo-automation-mcp` and
`cargo-teksilo-fmt` binaries on PATH (through `cargo install`). It lists the
changes and asks first; `-y` skips the question, `-i` asks per action and
shows the equivalent command, `--dry-run` only lists. `--agent` limits it to
the project's agent instructions, `--user` updates the ones under the home
directory instead. It installs nothing that is not there, and refuses when
the tool is older than the app. You cannot answer its question: run it with
`--dry-run`, show the user the list, and run it again with `-y` once they
agree. Afterwards, ask them to restart your session (and the MCP server, if
`teksilo-automation-mcp` was replaced), since this one still has the old
instructions.
- **`model fetch`** explicitly downloads the semantic search model. Search uses
BM25 until it is cached; a lookup never starts a download.
- **`status --json`** reports the resolved version and installed tooling.
Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/teksilo/reference/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Install the server matching the framework version your app pins:
cargo install teksilo-automation-mcp --version <the teksilo version your app pins>
```

`cargo teksilo status` says when the server on PATH is from an older or another
release, and `cargo teksilo update` replaces it.

Then:

```bash
Expand Down
13 changes: 12 additions & 1 deletion .claude/skills/teksilo/reference/teksilo_app_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ opt-outs:
| `async` / `tokio` / `async-std` | Optional main-thread async executor (`.install_async()`, `ctx.spawn_local`, `spawn_blocking`). `tokio` / `async-std` imply `async` and add the reactor (`.install_async_tokio()` / `.install_async_async_std()`) so a handler can `.await` that runtime's futures: timers, sockets, `reqwest`. Off by default. See `cargo teksilo show docs/async.md` |
| `terminal` | Embeddable `Terminal` widget: a real shell over a PTY. Off by default |
| `automation` | Debug-only automation bridge (`.install_automation_bridge_in_debug()`) for agents and tests. Off by default |
| `theme-material3` / `theme-fluent` / `theme-macos` | Material 3, Fluent (Windows 11) and macOS Aqua presets, as `material3::light()` etc. from the prelude |
| `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` | Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) presets, as `material3::light()` etc. from the prelude. `theme-kde` also adds `.native_theme_kde()` (see Theming). Its font features (`bundled-fonts`, `system-fonts`) are not forwarded: depend on `teksilo-theme-kde` directly for Plasma's own faces |
| `telemetry` | Privacy-respecting analytics wiring + `PrivacySettings` widget |
| `fonts-cjk-sc` / `fonts-thai` / `fonts-all` / `system-emoji` | Extra bundled script fonts / runtime color-emoji fallback |

Expand Down Expand Up @@ -363,6 +363,17 @@ let theme = intui::light(); // or intui::dark()
`ctx.set_theme(...)` updates an internal signal and dirty-marks nodes — no rebuild, and
focus/scroll/interaction state survive.

**Following the desktop.** `.theme_mode(ThemeMode::FollowSystem)` picks Int UI light or
dark by the desktop's preference; `ThemeMode::Native` adopts the desktop's own colours
(accent, surfaces, selection; on a KDE Plasma session its colour scheme) in Int UI's
shapes, and both track the desktop while the app runs. A handler
switches to Native with `ctx.follow_system_theme()` and back with `ctx.set_theme(..)`. With
the `theme-kde` feature, `.native_theme_kde()` (the prelude's `TeksiloAppBuilderKdeExt`)
makes a window *built* in Native mode on a Breeze desktop use the desktop's fonts and
animation speed and Breeze's shapes as well; any other desktop keeps Int UI in its colours. A theme change after the window is built is a
re-tint: the shapes stay those it was built with. `.native_theme(|request| ..)` installs
a factory of your own. Reference: `cargo teksilo show docs/native-theme.md`.

**Variants** select a widget's look (Tier 1):

```rust,ignore
Expand Down
102 changes: 102 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,18 @@ jobs:
- name: Test
run: cargo test --workspace

# teksilo-theme-kde's two font features are off by default and nothing
# in the workspace turns them on, so the workspace steps above build and
# test only the build without them. `bundled-fonts` embeds the faces and
# adds `font_registrar()`, `system-fonts` only names the families. With
# both on, every item and test gated on either feature compiles and
# runs (tests/fonts.rs checks the registrar's faces under the first);
# the workspace steps cover the ones gated on neither.
- name: Check and test teksilo-theme-kde (bundled-fonts, system-fonts)
run: |
cargo check -p teksilo-theme-kde --all-targets --features bundled-fonts,system-fonts
cargo test -p teksilo-theme-kde --features bundled-fonts,system-fonts

# cargo-teksilo's `semantic` feature (search embeddings) pulls in
# fastembed -> ort (ONNX Runtime) plus the onig_sys / esaxx-rs C/C++ sys
# crates. If that build ever breaks on some platform, `cargo install
Expand Down Expand Up @@ -666,6 +678,96 @@ jobs:
fi
echo "package size within guard"

# The same guard for the two KDE crates. teksilo-theme-kde embeds
# Plasma's Noto Sans and Hack under `fonts/` (about 5.6 MB of faces,
# 3.2 MiB packaged), so a face added there moves it towards crates.io's
# 10 MB limit; the fallback, should it ever pass the guard, is Latin
# subsets of the faces. teksilo-kde-scheme is small, and is measured
# because it is packaged here anyway: teksilo-theme-kde depends on it at
# the same version, which crates.io has only once it is published, so
# the two are packaged together.
#
# `--exclude-lockfile`: cargo resolves a package's Cargo.lock against
# the crates.io index, with only the packages named on the command line
# supplied locally. On a release tag, where the Release workflow runs
# this file before publishing, teksilo-core and the other workspace
# crates at the new version are not on crates.io yet, so the resolution
# would fail and block every release. The lockfile adds about 40 KB to
# the compressed teksilo-theme-kde package, far inside the margin
# between the 9 MiB guard and the 10 MB limit.
#
# The listing then checks what the package holds: every tracked file
# under teksilo-theme-kde's `fonts/` (the faces and their licences, which
# the OFL and the Hack licence require to travel with the faces), and,
# in either crate, no test fixture (teksilo-kde-scheme's vendored Breeze
# schemes are LGPL and test-only), no `tools/` and no golden `vectors/`.
- name: KDE crates package guard
if: matrix.name == 'debug'
run: |
rm -f target/package/teksilo-theme-kde-*.crate target/package/teksilo-kde-scheme-*.crate
cargo package -p teksilo-theme-kde -p teksilo-kde-scheme --no-verify --allow-dirty --exclude-lockfile

shopt -s nullglob
guard_mib=9
guard=$((guard_mib * 1024 * 1024))
failed=0
for name in teksilo-theme-kde teksilo-kde-scheme; do
crates=(target/package/"$name"-*.crate)
if [ "${#crates[@]}" -ne 1 ]; then
echo "::error::expected exactly one $name .crate, found ${#crates[@]}: ${crates[*]}"
ls -la target/package/ || true
exit 1
fi
crate="${crates[0]}"
bytes=$(( $(wc -c < "$crate") ))
mib=$(awk -v b="$bytes" 'BEGIN { printf "%.2f", b / 1048576 }')
echo "$crate is ${bytes} bytes (${mib} MiB); guard ${guard_mib} MiB, crates.io limit 10 MB"
if [ "$bytes" -gt "$guard" ]; then
echo "::error::$name .crate is ${mib} MiB, over the ${guard_mib} MiB guard"
failed=1
fi
done

theme_list=$(cargo package -p teksilo-theme-kde --list --allow-dirty)
scheme_list=$(cargo package -p teksilo-kde-scheme --list --allow-dirty)

# The expected font files are the tracked ones, so adding or
# renaming a face needs no edit here; an emptied `fonts/` would pass
# that vacuously, hence the two counts.
mapfile -t fonts < <(git ls-files -- crates/teksilo-theme-kde/fonts | sed 's|^crates/teksilo-theme-kde/||')
faces=0
licences=0
for f in "${fonts[@]}"; do
case "$f" in
*.ttf) faces=$((faces + 1)) ;;
*LICENSE*) licences=$((licences + 1)) ;;
esac
if ! grep -qxF -- "$f" <<< "$theme_list"; then
echo "::error::teksilo-theme-kde's package is missing $f"
failed=1
fi
done
if [ "$faces" -eq 0 ] || [ "$licences" -eq 0 ]; then
echo "::error::crates/teksilo-theme-kde/fonts tracks ${faces} faces and ${licences} licences; expected at least one of each"
failed=1
fi
echo "teksilo-theme-kde packages ${faces} faces and ${licences} licences"

for name in teksilo-theme-kde teksilo-kde-scheme; do
if [ "$name" = teksilo-theme-kde ]; then list=$theme_list; else list=$scheme_list; fi
stray=$(grep -E '(^|/)(fixtures|tools|vectors)(/|$)' <<< "$list" || true)
if [ -n "$stray" ]; then
echo "::error::$name's package carries files that must stay in the repository:"
echo "$stray"
failed=1
fi
done

if [ "$failed" -ne 0 ]; then
exit 1
fi
echo "KDE packages within guard, with their fonts and licences and without fixtures, tools or vectors"

# cargo-teksilo embeds two copies of files that also live elsewhere in
# the repo, so an app that only depends on the `teksilo` crate — not
# this checkout — still gets them. Nothing else enforces the copies
Expand Down
Loading
Loading