Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
0.7.4 - 2026-05-08¶
Added¶
output.style.overviewconfig group for overview-image specifics:layer_order("descending"|"ascending", default"descending") — direction the multi-layer overview stacks its per-layer rows.thumb_position("top"|"bottom"|"follow", default"follow") — where the THUMBS row sits in the rendered stack."follow"keeps the row directly under the layer it represents.thumb_layer(int|null, defaultnull) — which QMK layer the thumb cluster is sourced from.nullresolves at render time to the last rendered layer fordescendingand the first forascending, so the default works for keymaps that don't define QMK index 0.
Changed¶
output.style.layer_connectormoved tooutput.style.overview.layer_connector— the dotted connector paths only render in the overview image, so the field now lives alongside the other overview options. The schema, docs, and TUI write paths are updated accordingly.- Thumb-cluster geometry overhaul — the rendered thumb cluster now has uniform spacing and exact slant-matching:
- Pad / Nail / Knuckle slants are now derived from Down's edge slope
(
(_DOWN_SLANT_MULTIPLIER / 2) / _DOWN_HEIGHT_RATIO) rather than hand-tuned per key, so each key's slanted edge is exactly parallel to Down's. Previous per-key values were 1.09–1.35× too steep, visible as a subtle taper along each seam. - Pad / Nail / Knuckle's perpendicular distance to Down is now
exactly
thumb_key_gap(replaces the previous axis-alignedthumb_key_gap / 2offset, which produced gaps in the 0.27–0.37 ×thumb_key_gaprange and varied by key). - Up / Knuckle's vertical distance to Pad / Nail is now exactly
thumb_key_gap(replaces the previouscenter_y − inset × 1.5anchoring). - Pad's width is now derived as
Knuckle.width − thumb_key_gap / 3, matching the original Svalboard documentation image proportion. The hard-coded_PAD_KEY_PROPORTION = 0.38constant was removed. - DD and UP cutout outsets in Down are now both
thumb_key_gap × 0.4rather than per-key proportions of each cutout's own width (formerly 0.05 of DD width and 0.025 of UP width). The rim band now scales with the user-configured gap. _THUMB_INSET_PROPORTIONreduced from0.038to0.038 × 2/3(≈0.02533) — the new perpendicular-gap layout puts more whitespace at every seam, so the gap itself wants to be tighter for the cluster to read at the same overall density.- Down rendered as true Boolean subtraction. Down's painted
<path>is computed asDown minus DD minus UPvia Skia PathOps (newskia-pathopsdependency, distributed as binary wheels). Replaces the previous<clipPath>+fill-rule="evenodd"approach — XOR fill produced spillover where UP's outset extended past Down's outline. The new path has the holes baked into the geometry, so a stroke (or any other outline-aware effect) traces the actual painted shape, including the cutout rims. Trapezoid.outset(amount)andTrapezoid.translated(dx, dy)added on the existingTrapezoidprimitive.outsetis a true Minkowski offset (every edge moves perpendicular byamount, corner radii grow byamount);translatedrebuilds at a shifted origin. Replaces the previous parameter-inflation approach which diverged from the true offset on slanted edges and didn't adjust corner radii.
Fixed¶
-l/--layerfilter now restricts the overview image too. Combining per-layer indices with-l overview(e.g.-l 1,3-5,8 -l overview) previously rendered the requested per-layer images correctly but the overview image still painted every populated layer in the keymap. The filter now propagates intodraw_overviewso the overview shows only the same subset of layers — and the symbol legend pulls only from those layers.-l overviewalone (no per-layer filter) is unchanged.
Removed¶
- Internal: deleted the inline
publish-packagejob from.github/workflows/release.yml. Under the newjust releaseflow, PyPI publishing is owned exclusively by.github/workflows/publish.yml(triggered by therelease: publishedevent thatgh release createemits). The inline job was dead code —release.ymlonly ever seespyproject.tomlat a.devversion on mainline HEAD now, so theis_prerelease=falsegate never fires. Header comments on both workflow files spell out the new ownership.
0.7.3 - 2026-05-08¶
Added¶
output.layout.spacingblock with 15 configurable fields covering every gap, padding, and inset the renderer paints. Each field accepts one of three forms following a single magnitude rule:- Float
< 1.0— proportion of the field's base (usually doc width). - Float
≥ 1.0— absolute SVG units. - String
"N%"— shorthand forN / 100(proportion form). null— the field's built-in default proportion.
Fields: margin, inset, column_gap, section_spacing,
section_title_rule_gap, table_header_spacing, table_col_spacing,
table_row_spacing, finger_key_gap, thumb_key_gap,
layer_indicator_spacing, chip_padding, tap_dance_pill_padding,
macro_action_inset, layer_badge_inset. See
docs/configuration/config-file.md for per-field illustrations.
- output.style.strokes block for stroke-width styling — fields
chip_outline (1.2/1600 default) and header_rule (1.2/1600
default). Both follow the same magnitude rule as the spacings.
- output.style.layer_connector block with show (default true),
width (4.375/1600 default), and dot_spacing (12.25/1600 default)
for the overview's dotted connector paths.
- output.style.layer_indicator block with show (default true)
and width (2.0/1600 default) for the layer-indicator badges.
- output.style.legend_tables block grouping the three legend
sub-blocks: macros, tap_dances, symbols. Each sub-block carries
show (default true) and scale (default 1.5); the symbols
sub-block also carries flow and columns.
- just docs-gen-images recipe regenerates every image embedded in
the published docs in one command — option-images, spacing-mocks,
screenshots, plus docs/_static/keymap-1.svg and
docs/_static/svalboard-overview.svg rendered from
samples/config/SvalCOLEMAK-config.yaml +
samples/keymaps/SvalCOLEMAKDHM.vil. The release recipe now invokes
it instead of running option-images + screenshots separately, so
spacing mocks and the keymap images stay coupled to the renderer on
every release.
- scripts/release_changelog.py promote X.Y.Z rewrites
CHANGELOG.md for a release: renames ## [Unreleased] to
## [X.Y.Z] - YYYY-MM-DD, inserts a fresh empty ## [Unreleased]
block above it, and updates the link-reference list at the bottom
(rewrites [Unreleased] to compare vX.Y.Z...HEAD and inserts a new
[X.Y.Z]: ...compare/<prev>...vX.Y.Z entry, where <prev> is parsed
from the previous [Unreleased] link). Idempotent — aborts if a
[X.Y.Z] section already exists. The release recipe now invokes it
so the changelog stays in sync with the released version
automatically.
Changed¶
output.style.border.widthaccepts the same magnitude rule as the spacings — float< 1is a proportion of the doc width, float≥ 1is absolute SVG units,"N%"is the proportion shorthand. Default value (2 SVG units) is unchanged.- Section title strips top-anchor the title text (no more dangling
whitespace above the title); the strip's height equals
title_font_size + section_title_rule_gap. - The overview's layer badge splits its label into a right-aligned
index column and a left-aligned name column, with the index column
sized to fit the widest index across the rendered layers. Variant
labels now align under the name column (or at the leading inset for
auto-named
LAYER Nbadges, matching the legacy single-text layout). - The overview's named-macro hairline rule weight is now unified with the section-stripe rule weight (1.2/1600, was 1.0/1600).
- The overview's layer-indicator gap is now uniform across finger and thumb clusters (12/1600 of doc width). Previously each cluster type used its own cluster-relative ratio that produced different gaps.
MacroPillnow positions the action icon's left edge atmacro_action_insetfrom the pill border (centre atinset + icon_w/2) instead of its centre. Result: the visible whitespace before the icon, between icon and text, and between text and pill border are all uniformlymacro_action_insetwide, and the text-centring math no longer leavesicon_w/4of slack on each side of the label.MacroRownamed-header chip→name gap switched fromchip_paddingtotable_header_spacing. The chip→name gap is conceptually a "row-header → row-content" boundary — the same roletable_header_spacingalready plays for column-header→first-row and chip→first-cell.chip_paddingis the symmetric inset inside a chip (border ↔ glyph / name text), not a between-elements gap.using_render_contextaccepts an optionalfont_usage_collectorparameter so callers that compose multiple panels under differentctxinstances can share one collector and get the union of every panel's character usage when embedding fonts. Default behaviour (a fresh per-block collector) is unchanged.
Fixed¶
- Connector-router source rects in
KeymapOverviewwere keyed byid(key). Reusing the sameSvalboardTargetKeyinstance across multiple cluster slots (e.g. bindingLT(NAV) = SvalboardTargetKey(label="LT(NAV)", layer_switch=1)to both thumb pads) collapsed every reuse onto a single dict slot — only one connector ever painted, from whichever slot wrote the rect last. EachConnectorStepnow carries itssource_layerandsource_side, and the rect dict is keyed by structural position (finger.<source_layer>.<cluster_attr>.<slot>/thumb.<source_layer>.<source_side>.<slot>), so every source slot gets its own indicator regardless of the underlying key's Python identity. Spacing.finger_key_gapsource docstring corrected from3.8%to1.8%— the renderer uses_INSET_WIDTH_PROPORTION = 0.018, and the user-facing reference already documented1.8%.samples/config/{skim,SvalCOLEMAK,SvalQWERTY}-config.yamlmigrated to the current nested schema. The samples were silently broken — Pydantic rejected the legacy flat fields (show_layer_indicators,show_layer_connectors,show_special_keys_legend,show_symbol_legend,symbol_legend_flow,symbol_legend_columns,macros_scale,tap_dances_scale,symbols_scale) asextra_forbidden, soskim generate -c samples/config/...yamlfailed validation. The samples now exerciselayer_indicator.show,layer_connector.show, andlegend_tables.{macros,tap_dances,symbols}.- CHANGELOG dates for the
0.7.0and0.7.2releases were inverted; corrected to chronological order (0.7.0 on 2026-05-03, 0.7.2 on 2026-05-04). - CHANGELOG link-reference list at the bottom of the file was stale —
the
[Unreleased]link pointed atcompare/v0.5.3...HEADdespite v0.7.2 being the latest release, and[0.5.4],[0.5.5],[0.7.0],[0.7.2]had no entries. Backfilled the missing entries and rewrote[Unreleased]to comparev0.7.2...HEADso the existing links resolve correctly on GitHub.
Documentation¶
output.style.palette.layersreference now accurately describes the gradient auto-derivation behaviour (the keymap-generation pipeline replacesgradient: nullwithmake_gradient(base_color, color_index)before rendering, so the rendered output always sees a 6-stop tuple).color_index's dual role as gradient anchor and primary-stop selector is explicit. Two double-south finger-cluster images illustrate the auto-derived and explicit gradient YAML forms side by side.- Added fully-coloured sample images under each
output.style.legend_tablessub-block (macros,tap_dances,symbols) — the macros and tap-dances samples mix named and unnamed entries so the named-vs-unnamed layout split reads at a glance. - Chrome-colour table now carries inline colour swatches next to each default value.
- Tables now keep all columns single-line except the description column, which absorbs the wrapping. Stops the renderer from choosing a Type / Default column to wrap when the description column is long.
- Spacing-mock illustrations refined: every band aligns precisely with the option it represents (no overlap with borders, glyphs, or labels), Nerd-Font glyphs render across all viewers (bundled fonts subset and embedded), and several sets of related mocks (section-spacing / section-title-rule-gap / table-*-spacing / header-rule-stroke) share a single canvas shape so the docs render them at matching font sizes.
docs/configuration/cli-options.mdis now a full reference for everyskimsubcommand —generate,configure,doctor— plus the global--version,--verbosity, and--quietflags. Each flag carries the canonical type table, an explicit anchor ID, and (where the flag drives a config field) a link to the matching field inconfig-file.md. Added the missingkeyboard.features.double_southanchor inconfig-file.mdso the cross-reference resolves.docs/configuration/cli-options.mdanddocs/configuration/config-file.mdnow insert a---separator above every H2/H3/H4 heading (and every H5 insideconfig-file.md, where each H5 documents its own reference field), matching the visual rhythm already used indocs/configuration/configurator-ui.md.docs/configuration/cli-options.mdscale-flag ranges (--macros-scale,--tap-dances-scale,--symbols-scale) corrected from> 0.1to≥ 0.1—click.FloatRange(min=0.1)is inclusive.docs/configuration/config-file.mdbundled-fonts list now includes JetBrains Mono Nerd Font Mono (used by the Configurator UI screenshot pipeline; previously omitted).docs/api/ui.mdheading "Style Tab" → "Output Tab" so the heading matches the actual module (skim.tui.output_tab) and the UI label.docs/api/application.mdadds aDoctorsection soskim.application.doctoris rendered alongside the other application modules.docs/_static/keymap-1.svganddocs/_static/svalboard-overview.svgregenerated against the current renderer. Previously the files were hand-copied from a one-shot run; they are now reproducible viajust docs-gen-images.
Removed¶
Important
The renames below are breaking schema changes. Existing YAML configs that set any of the listed fields will fail validation against the new schema. Pre-1.0 migration is by manual rewrite — no deprecation aliases are shipped. Update your config to the new nested paths.
output.style.macros_scale→output.style.legend_tables.macros.scaleoutput.style.tap_dances_scale→output.style.legend_tables.tap_dances.scaleoutput.style.symbols_scale→output.style.legend_tables.symbols.scaleoutput.style.show_special_keys_legend→ splits intooutput.style.legend_tables.macros.showandoutput.style.legend_tables.tap_dances.show. Set both to the same value to preserve the legacy combined behaviour, or set them independently to selectively hide one legend.output.style.show_symbol_legend→output.style.legend_tables.symbols.showoutput.style.symbol_legend_flow→output.style.legend_tables.symbols.flowoutput.style.symbol_legend_columns→output.style.legend_tables.symbols.columnsoutput.style.show_layer_connectors→output.style.layer_connector.showoutput.style.show_layer_indicators→output.style.layer_indicator.show- (Internal): the legacy field
Style.name_gapwas renamed tochip_paddingand its default value doubled (10/1600 → 20/1600) so the derivedchip_vertical_padding = chip_padding * 0.25matches the legacy chip height. Visible name-area horizontal padding inside TD chip outlines therefore widens. output.style.palette.overridesfield removed entirely. The feature was added before CSS-named-color support ("crimson","steelblue", etc.) was wired throughwebcolors.name_to_rgb, intended to let users redefine the W3C named colors. In practice the resolved override dict was never consumed by the rendering pipeline —parse_coloralways reached straight for the W3C values — so the feature has been a no-op since 0.5.0. Existing YAML configs that includepalette.overrideswill fail validation; delete the block.skim configure --qmk-color-header/-Cflag removed. The flag parsedHSV_*/RGB_*#defines out of a QMKcolor.hfile and dumped them intopalette.overrides. With that field gone, the import has no destination.- Internal:
ConfigGenerator._parse_qmk_colorsand theqmk_header_contentparameter ongenerate_from_keybardremoved alongside the CLI flag. - Internal: dropped three unused TUI screenshot shots (
keycodes-tab,output-tab,output-style-tab) fromscripts/screenshots.pyand the corresponding orphan SVGs fromdocs/_static/tui/. They were generated byjust screenshotsbut no documentation referenced them.
0.7.2 - 2026-05-04¶
Fixed¶
hold_symbol_positionnow actually reorders hold/tap labels. The setting was silent for the entire 0.7.0 lifecycle:_adjust_hold_symbol_labelchecked for the ASCII pipe|while real labels carry the box-drawing│(U+2502), so the function early-returned every time. Once the swap path runs, the semantics are now cluster-relative —OUTWARDputs HOLD on the side of the cluster the key sits on (LEFT-side keys keep the defaultHOLD│TAPorder; RIGHT-side keys swap toTAP│HOLD). Applies uniformly to finger east/west keys and to thumb pad / up / nail / knuckle keys regardless of which keyboard half the cluster sits on.- Missing modifier-union mappings for the L/R OPT, CMD, and WIN
aliases (
MOD_LOPT,MOD_LCMD,MOD_LWIN,MOD_ROPT,MOD_RCMD,MOD_RWIN) so labels referencing those modifiers resolve to the same glyph as theirALT/GUIcanonical forms.
Changed¶
- Thumb cluster label alignment is now uniformly centred for the Pad, Nail, Knuckle, Down, and Double-down keys. Up keeps its inward-aligned label (the trapezoid wants the text on the wide outer face). Previously Pad / Nail / Knuckle anchored to the slanted edge, which read inconsistently next to the centred Down / Double-down labels.
0.7.0 - 2026-05-03¶
This release replaces the imperative SVG renderer with a composable rendering framework, adds automatic font subsetting, and ships a wave of layout-quality fixes.
Added¶
- Composable rendering framework. Every image — per-layer keymaps,
multi-layer overview, macros, tap-dances, special-keys, symbols — is
now built as a tree of small, single-purpose composables that report
a
Sizeplus adraw_at(d, origin)painter. Replaces ~5K lines of positional layout math with a uniform tree-building model. RenderContextcarries config / keymap / theme / document metrics via aContextVar;@Composable(use_context=True)factories receive it implicitly.MetricsComponent[M]lets composables expose typed metrics (e.g.,LayerIndicatorMetrics.routing_origin,FingerHalfMetrics.overflow_size) without exposing internals.- Overflow-aware geometry — every cluster / half reports
(size, overflow_size, overflow_offset)so parents can place chrome like layer-switch indicators without hidden assumptions. - Single-page documents:
KeymapLayerDocument,KeymapOverviewDocument,KeymapMacroDocument,KeymapTapDanceDocument,KeymapSpecialKeysDocument,KeymapSymbolDocument. Each entry point reduces torender(<Document>(...)). - Automatic font subsetting in SVG output.
FontUsageCollectorrecords every character painted viaAdjustableText/RichText/ the title text; on render, only the glyphs actually used are embedded. SVG output sizes drop dramatically (overview: ~5MB → ~360KB; per-layer: ~104KB → ~89KB).--use-system-fontsskips embedding entirely. AdjustableTextandRichTextcomposables. Single-style and multi-span text with synchronised shrink-to-fit, per-span proportionalmin_font_sizefloors, and ellipsis truncation. RichText accepts a markup string with Nerd Font tokens (%%nf-md-keyboard;) and the│separator character; the parser handles font routing (Symbols / Unicode Symbols fallbacks) automatically.- Transparent fall-through rendering —
KC_TRANSPARENT/KC_TRNS/_______keys on layers above 0 borrow the layer-0 label and are drawn as a faded "ghost". - Ghost text colour is the key's fill colour with HSL lightness shifted by 0.12 — lighter when the fill sits at or below the layer's base colour in the gradient, darker when it sits above.
- When the layer-0 source maps to a layer change (
MO(),LT(), etc.), the ghost key inherits thatlayer_switchand is treated as a layer-triggering key — getting the destination layer's background colour, indicator badge, and overview connector. - New
style.show_transparent_fallthroughoption (defaulttrue) lets users opt out and revert to blank transparent keys. TUI configurator gets a matching toggle and contextual help blurb. - Per-image body-scale flags for the standalone images:
--macros-scale,--tap-dances-scale,--symbols-scale. Body content (chips, pills, glyphs, symbol descriptions) scales by the multiplier; chrome (title, footer, padding) stays at the unscaled per-image size. CLI overrides matching config fields persist on the resolvedStyle. - Symbols image and symbol legend. A new standalone
keymap-symbolsimage plus a per-layer / overview symbol legend. Walks each key's raw keycode, recurses through wrapper functions (LT(N, KC_A)), tap-dance variants, and macro action keys; resolves alias chains; renders one row per distinct canonical keycode or function name. Categories are read fromkeycode-mappings.yaml'ssymbol_descriptionsinsertion order — edit the YAML to reorder legend groupings. --symbol-columnsflag forces an exact column count (canvas shrinks to the resulting width).--no-symbolsopts out of the legend.- Cross-layer connector routing in the overview. Multi-source Phase-1/Phase-2 router that draws dotted lines from each layer-trigger key (in finger or thumb clusters) to its target layer's row. Paths share routing columns when their Y-spans don't overlap; paths to the same target merge their final horizontal segment so a single visible LEFT segment connects all sources to that layer's row. Special-cases: LT_Up/LT_Down on the same thumb cluster, S+DS triggers on non-R4 finger clusters.
- Auto-derived configuration from the input keymap when
--configisn't provided. - DejaVu Sans Mono / Condensed as Unicode-symbol fallback —
covers
⎇,│, and other characters Roboto doesn't carry. - Per-image
wrap_contentsemantics onMacroSection,TapDanceSection,SymbolSection. The standalone images snug the canvas to natural content width; combined / per-layer documents inflate sections to fill the column budget.
Changed¶
- Layout overhaul of the multi-layer overview.
- Badge column → cluster column gap pinned at exactly
2 × column_gap. Indicators that bleed left of the keys-only edge paint visually into the gap (paint-only, no layout claim). - Central gap between the two halves pinned at
2 × column_gapmeasured between the finger halves' inward-indicator extents. - Thumb clusters align with the finger halves' keys-only edges
(left thumb's right edge with the left half's right edge; right
thumb's left edge with the right half's left edge). When BOTH
thumbs have inward-bleeding indicators (knuckle / nail keys
with a
layer_switch), the central column widens so the visible chrome-to-chrome gap is at leastcolumn_gap. - Layers render highest-to-lowest QMK index — most-specialised layers read first, layer 0 at the bottom.
- Layer-row spacing is indicator-aware; canvas extents account for layer-switch indicator circles that overflow cluster bounds.
- Default canvas width bumped from 800 to 1600 SVG units.
- Title and copyright font sizes are now doc-width-proportional (~31pt title, ~17pt copyright at the canonical width).
- Spacing convention is uniform across every image.
insetfor vertical spacing (Column gaps, outer chrome border-to-content gap),column_gapfor horizontal spacing (inter-cluster gap inside a half),2 × column_gapfor primary visual dividers (central halves gap, badge → cluster gap, macro/TD pair gap). - Connector spacing pinned at
0.6 × outer_key_widthfor tighter, more readable lane / column placement. - Macro / tap-dance display.
- Named macros sort first (named-block then unnamed-block,
separated by
section_spacinginstead of the usual row gap). - Tap-dance name column resizes dynamically to fit the longest name within the available budget; per-row ellipsis truncation when names still overflow.
- Macro pills and tap-dance cells render through
RichText, so labels containing Nerd Font tokens render with the right per-span fonts. - Function-description templates in
keycode-mappings.yamluse@N;/#N;placeholders for argument substitution and|;for the separator character. Per-instance descriptions (e.g. specific layer numbers) and generic descriptions (one entry per function name) are both supported. - Renamed CLI flag
--keybard-keymap→--keymap. - Render-engine selection via
--render-engine chromium|cairo. The Cairo path converts text elements to SVG paths so output rasterises consistently regardless of which fonts the system has installed.
Fixed¶
- Title text now correctly renders in Roboto Thin — the title
characters are registered with the font subsetter so the embedded
@font-facerule is included in the output SVG. --widthis honoured as the final SVG / PNG canvas width.- Tap-dance name truncation measures actually-painted text width instead of overshooting from font-metrics estimates.
- Symbol legend's
symbol_legend_aliasesapply to function-call patterns (e.g.A(KC_RGHT)resolves toA(KC_RIGHT)'s entry). - F-keys in the
c2json-sample.jsonFunction Keys layer are positioned correctly (previously mirrored across the right cluster's E↔W axis). skimprompts for overwrite confirmation via/dev/ttywhen stdin is a pipe.
Internal¶
- Module structure consolidated.
text.py→font.py;legend.py→section_data.py(now also hosts the symbol-entry resolution layer that previously lived in a now-deletedsymbol_legend.py).text_to_paths.pymoved fromrender/toexporter/(its only consumer is the Cairo exporter).layout.pydeleted. - All concrete document composables consolidated in
keymap_document.py. Body composables stay in their per-image modules (keymap_layer.py,keymap_overview.py). - Entry points in
__init__.pyown all data prep — pre-resolving macros / tap-dances / symbol entries before passing pre-resolved lists to the document composables. Documents are pure layout consumers. __all__exports tightened across the rendering layer to advertise only what other production modules actually import.
0.5.5 - 2026-04-26¶
Added¶
- Multi-source overview connector routing — dotted lines from each layer-trigger key (in finger and thumb clusters) to its target layer's row.
- Per-layer finger-cluster routing across every rendered layer;
MO()/LT()/ equivalent layer-switch keys produce a connector regardless of which layer they live on. - Thumb-cluster routing with the LT_Up + LT_Down LEFT-DOWN special case (the LT_Up path detours west of LT_Down's drop column when both trigger).
- Non-R4 finger clusters with both
south_keyanddouble_south_keytriggering produce a RIGHT-DOWN-RIGHT South path that clears the DS drop column. - Global Phase 2 column allocation: paths from all sources share routing columns when their vertical Y-spans don't overlap, so column count tracks Y-overlap instead of total path count.
- Multi-target merge: paths targeting the same layer share a final horizontal segment, so a single visible LEFT segment connects all sources to that layer's row.
- Macro (
M1–M50) and tap-dance keycode mappings, rendered with distinct nf-md icon glyphs in the overview. - Synthetic Vial fixture (
tests/integration/fixtures/connector-routing-coverage.vil) plus structured-assertion integration tests covering R4 / non-R4 / S+DS / multi-layer routing.
Changed¶
- Connector spacing pinned at
0.6 × outer_key_widthfor tighter, more readable lane/column placement. - Layer-badge column gap pinned at
4 × KEYMAP_SPACINGso the visual rhythm tracks the connector router instead of canvas-relative inset units. - Layer badges now align exactly with the West finger key in their row — same Y (top edge) and same height (
outer_key_size). - Default canvas width bumped from 800 to 1600 SVG units for better legibility.
- Inter-row spacing rhythm in the overview is now consistent regardless of Double-South presence.
- Layer-row spacing is now indicator-aware — canvas extents account for layer-switch indicator circles that overflow cluster bounds.
skimderives a default config from the input keymap when--configisn't provided.
Fixed¶
skimnow prompts for overwrite confirmation via/dev/ttywhen stdin is a pipe (previously silently overwrote or hung).- F-keys in the
c2json-sample.jsonFunction Keys layer were mirrored across the right cluster's E↔W axis (F1↔F4 and F2↔F3); now positioned correctly.
0.5.4 - 2026-04-24¶
Fixed¶
- Pydantic 2.13+ compatibility:
SplitSidePositionenum used as aBeforeValidatorcaused a crash on Python 3.12+ due to stricter validator signature inspection.
Changed¶
- Development environment now uses the latest Python version (via mise) to catch compatibility issues earlier. The project still supports Python 3.10+.
0.5.3 - 2026-04-24¶
Added¶
- Contextual help system for the TUI configurator — press F1 or Alt+H on any field to see a help modal with field-specific documentation.
- Per-field markdown help content for all fields across Keyboard, Keycodes, and Style tabs.
- Vim-style scroll bindings in help modal (j/k, Ctrl+D/U, Ctrl+F/B, G/g).
- General help fallback with keyboard shortcuts overview.
show_layer_connectorsconfiguration option to toggle layer connector lines in overview images.--titleand--copyrightoptions toconfigurecommand.--layer-countoption with sparse fill logic forconfigurecommand.--keymapoption toconfigurefor Vial and c2json format support.- Layer move mode to reorder layers in list views.
- Custom
SkimButtonwidget with Space and Enter activation.
Changed¶
- Renamed
--keybard-keymapCLI option to--keymap. - Non-standard keycode scanning for configuration generation.
- Skip empty layers and simplify keycode override detection.
- Updated documentation: README, Sphinx docs, and example SVGs to reflect current features.
Fixed¶
- Directional focus navigation inside modal dialogs.
- QMK firmware index used for layer lookup, fixing overview rendering issues.
- Circular dots for overview layer connectors (previously rendered as pills).
- Footer keybind display order and styling standardised.
- GitHub Actions and PyPI publishing fixes.
0.5.2 - 2026-04-22¶
Fixed¶
- GitHub Actions and PyPI publishing fixes.
0.5.1 - 2026-04-22¶
Fixed¶
- GitHub Actions and PyPI publishing fixes.
0.5.0 - 2026-04-22¶
Added¶
- Interactive TUI for configuration editing via
skim configure --interactive. - Tabbed interface with Keyboard, Keycodes, and Style tabs.
- List/detail panels for layers, keycodes, and layer colors.
- Enter-to-edit, Escape-to-rollback field editing with commit/cancel hints.
- Add/Remove buttons for layers, keycodes, and layer colors.
- Color swatches and W3C named color suggestions for color input fields.
- Gradient preview on dark and light backgrounds with index numbers.
- Dynamic/Manual gradient type selector for layer colors.
- Keycode autocomplete and spatial arrow-key focus navigation.
- Save & quit dialog with confirmation.
- Overview image rendering with layer badges and cluster layout.
- Dotted connector lines from layer indicator circles to target layers.
- Intelligent connector routing with per-key escape directions.
- Layer indicator circles on individual layer images.
- Non-sequential layer index support (QMK index independent of config position).
- Layer index field editing with validation in TUI.
- Dict-based keymap layers in data model.
skim doctorcommand with cairo optional dependency check.ConfigGeneratorfor generating default and keybard-based configurations.- Font subsetting support via
FontUsageAnalyzerandFontSubsetter. subtitle(later renamed tovariant) field forKeyboardLayer.- Optional
copyrightfield in Output configuration. --interactiveand--configflags forconfigurecommand.
Changed¶
- Renamed
KeyboardLayer.subtitletovariant. - Extracted reusable
ListDetailPanebase class for TUI widgets. - Major overview layout overhaul with improved badge positioning and connector routing.
- Upgraded dependencies.
Fixed¶
- Resolved all ruff lint, formatting, and basedpyright errors.
- Granted
id-tokenpermission for PyPI trusted publishing. - Numerous TUI layout, focus, and interaction fixes.
0.4.4 - 2026-01-19¶
Added¶
- Support for QMK hold-tap keys (e.g.,
LT(1, KC_SPC),LSFT_T(KC_A)). - Support for complex macro functions via
macro_functionsmapping. - Support for modifier union arguments (e.g.,
OSM(MOD_LCTL|MOD_LSFT)).
Changed¶
- Updated config generator to create
USER##mappings for custom keycodes found in Keybard files. - Improved config generator to prevent duplicate custom key entries if they already exist in internal mappings.
- Refactored keycode transformer to use a unified, data-driven pipeline for resolving all function-style keycodes (modifiers, layers, macros).
- Modified the release workflow to call the publish workflow directly to guarantee PyPI always receives a new release version when there is one.
Fixed¶
- Fixed incorrect nesting of
named_colorsin generated configuration files (now directly underappearance). - Fixed issue where layer toggle targets were not identified when using aliased keycodes in
reversed_alias. - Fixed bug where
skim generatewould create files even when user aborted the overwrite prompt. - Fixed inconsistent behavior in
skim configureby adding an overwrite confirmation prompt (matchinggenerate). - Fixed false positive overwrite prompt in
skim configurewhen output is an existing directory (now targetsskim-config.yamlinside it).
0.4.3 - 2026-01-15¶
Added¶
- Version validation script (
scripts/validate_version.py) - Git hooks for release automation (
scripts/hooks/) - Hook setup script (
scripts/setup-hooks.sh)
Changed¶
- Release workflow now supports both dev and release versions from
pyproject.toml - Renamed
prerelease.ymltorelease.yml - Release automation via
reference-transactionhook auto-bumps and pushes dev version after release push
0.4.2 - 2026-01-15¶
Added¶
- Overwrite protection with interactive confirmation prompt when output files exist
--forceflag forskim generateto skip overwrite confirmation- OpenSpec framework for AI-assisted change management (
openspec/)
0.4.1 - 2026-01-14¶
Added¶
- Pre-release GitHub Actions workflow to auto-build versions on commit
- Package keywords and classifiers in
pyproject.toml - Automated coverage badge updates via pre-commit hook
- Sphinx documentation theme changed to Furo with GitHub integration
- New helper script
scripts/update_coverage.py
Changed¶
- Renamed package to
qmk-skim - Updated uv build configuration
- Updated README badges for coverage, build status, and PyPI version
0.4.0 - 2025-01-14¶
Added¶
- Initial public release of skim (Svalboard Keymap Image Maker)
- CLI tool for generating keyboard layout images from keymap files
- Support for Keybard (
.kbi), Vial (.vil), and QMK c2json (.json) formats - SVG and PNG output format support
- Layer selection options (all, overview, specific layers, ranges)
- Configuration file support for customizing appearance
- Configuration generator for extracting metadata from Keybard files
- QMK color.h import support with lightness/saturation adjustment
- Stdin support for piping keymap data
- Comprehensive test suite with 96% code coverage
- Google-style docstrings for all public APIs
- Sphinx documentation with GitHub Pages deployment workflow
- Pre-commit hooks for ruff formatting/linting and basedpyright type checking