Skip to content

Fun Layer pack + catalog collision tests#319

Merged
malpern merged 1 commit intomasterfrom
feat/m1-pack-fun-layer-and-collision-tests
Apr 24, 2026
Merged

Fun Layer pack + catalog collision tests#319
malpern merged 1 commit intomasterfrom
feat/m1-pack-fun-layer-and-collision-tests

Conversation

@malpern
Copy link
Copy Markdown
Owner

@malpern malpern commented Apr 24, 2026

Summary

Fun Layer pack — unblocked by giving the collection its own nested-layer activator (Leader → f → fun), same pattern as Numpad and Symbol. Right hand becomes an F-key grid (u/i/o = F7/F8/F9, j/k/l = F4/F5/F6, m/,/. = F1/F2/F3 + n/,// = F10/F11/F12), left hand is media/brightness.

Catalog collision tests (added before shipping Fun so the new activator is validated):

  • testNoTwoCollectionsShareAnActivator — guards against duplicate (sourceLayer, key) activator pairs. Allow-list for the three nav providers (Vim Nav / KindaVim / Neovim Terminal) which intentionally share Space→nav.
  • testNoTwoCollectionsMapTheSameInputInTheSameLayer — guards against silently-overridden mappings in the same target layer.
  • testEveryCatalogCollectionEnabledAtOnce — runs kanata --check on a config with every collection on.

Real bugs surfaced

The collision tests immediately caught two existing Mission Control issues from my earlier redesign:

  1. Activator collision: Mission Control had its own Space→nav activator that collided with Vim Nav's. Removed — MC is now purely additive, piggybacks on whichever nav provider is enabled. Pack description updated to note the dependency.
  2. Mapping collision: MC mapped e, [, ] in nav layer, conflicting with KindaVim's e=end-of-word / [=paragraph up / ]=paragraph down. Moved to unclaimed keys: q (Exposé), t (Show Desktop), , / . (prev / next Desktop). m (Mission) and c (Notifications) were already safe.

Test plan

  • swift test passes (420 tests)
  • Gallery shows Fun card in Layers category
  • Space + f + u emits F7 (when Vim Nav / KindaVim / Neovim Terminal is also on for the Space activator)
  • Mission Control's new keys (m/q/t/c/,/.) fire macOS shortcuts; no conflict with KindaVim if both are enabled

🤖 Generated with Claude Code

Adds the Function / Media pack (Leader → f → fun layer: right hand
becomes an F-key numpad grid, left hand is media/brightness). Closes
the "blocked" status of Fun Layer in the migration plan — just a
momentaryActivator away all along.

Before shipping the pack, adds three static guards to catch
collision-shaped bugs:

* `RuleCollectionCollisionTests.testNoTwoCollectionsShareAnActivator` —
  asserts only one collection owns each `(sourceLayer, key)` activator
  pair, with an allow-list for the intentionally-mutually-exclusive
  nav providers (Vim Navigation, KindaVim, Neovim Terminal all use
  Space→nav).
* `RuleCollectionCollisionTests.testNoTwoCollectionsMapTheSameInputInTheSameLayer` —
  asserts no two collections bind the same input key to different
  outputs within the same target layer.
* `RuleCollectionKanataValidationTests.testEveryCatalogCollectionEnabledAtOnce` —
  runs `kanata --check` against a config with every collection
  enabled, catching emergent combined-state issues.

The new tests surfaced real existing bugs in the current Mission
Control pack:
* Mission Control had its own Space→nav activator which collided with
  Vim Navigation's. Removed — MC is now an additive nav-layer pack
  that piggybacks on whichever nav provider the user has enabled.
* Mission Control mapped `e`, `[`, `]` in nav layer, which KindaVim
  already claims (`e`=end-of-word, `[`/`]`=paragraph up/down). Moved
  to unclaimed keys: `q` (Exposé), `t` (Show Desktop), `,` / `.` (prev
  / next Desktop). `m` and `c` were already safe.

Updated Mission Control pack copy and runtime behavior test to reflect
the new key layout and the nav-provider dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Apr 24, 2026

Code Review — PR #319: Fun Layer pack + catalog collision tests

Overview

Clean, well-motivated PR. The Fun Layer follows the exact same two-step pattern as Numpad/Symbol (Space → nav → f → fun), the Mission Control fixes resolve real bugs surfaced by the new collision tests, and the three new test types (activator uniqueness, mapping uniqueness, kanata --check all collections) fill a genuine gap in structural coverage. Overall shape is good. A few issues worth addressing before merge.


Issues

1. Fragile name-based lookup in activator collision test (Medium)

testNoTwoCollectionsShareAnActivator stores the collection name in claimed, then re-looks-up that name to retrieve the original collection's UUID for the nav-provider allow-list check:

var claimed: [String: String] = [:]          // key → collection.name
// …
if let existingCollection = collections.first(where: { $0.name == existing }),
   navProviderIDs.contains(existingCollection.id)

If any nav-provider collection is renamed the allow-list silently stops working — the lookup returns nil, the condition falls through to collisions.append(…), and tests start failing for the wrong reason. Store the UUID alongside the name so the allow-list check doesn't need a secondary O(n) name-scan:

var claimed: [String: (name: String, id: UUID)] = [:]
// …
claimed[key] = (collection.name, collection.id)
// …
if navProviderIDs.contains(collection.id),
   let existing = claimed[key],
   navProviderIDs.contains(existing.id)
{
    continue
}

2. Silent breakage for existing Mission Control users (Medium)

Removing momentaryActivator from Mission Control is the correct long-term fix, but existing users who had MC enabled with no nav provider will get a non-functional pack after this update with no in-app warning. The shortDescription update is good for new installs, but saved configurations won't re-surface that description until the user opens the pack detail.

Worth considering: a validation pass in InstallerEngine.inspectSystem() or a pack-dependency check in KanataViewModel that can flag "Mission Control requires a Leader pack". Even a simple requiredCollectionIDs: [UUID] field on Pack that the UI can use to show a dependency badge would prevent silent failures. This doesn't need to block the PR, but it should land soon after.

3. Multi-line comment blocks in production/test files (Low)

CLAUDE.md is explicit: "Never write multi-paragraph docstrings or multi-line comment blocks — one short line max." A few spots in this PR add multi-line blocks:

  • RuleCollectionCollisionTests.swift — 10-line class-level doc comment
  • RuleCollectionCatalog.swift — 4-line inline comment block on the MC collection
  • PackRuntimeBehaviorTests.swift — 3-line inline comment on the vimNavigation addition

These should be trimmed to a single explanatory line each.

4. testEveryCatalogCollectionEnabledAtOnce silently skips in most CI runs (Low)

The XCTSkip guard is appropriate, but this means the test is only meaningful if the CI runner has kanata available via KEYPATH_KANATA_PATH. The PR description's test plan says "420 tests pass" — but this test may not be among them. Worth confirming that CI actually exercises it; if not, a note in README or the test comment about how to opt into this check would help future contributors know it exists.


What's Good

  • Fun Layer structure is textbook — consistent with Numpad and Symbol in every dimension (activation pattern, icon choice, shortDescription format, longDescription: "").
  • Mission Control key reassignments (e→q, [→,, ]→., b→t) are well-chosen; the comment explaining the "unclaimed keys" reasoning is helpful.
  • testFunLayerUEmitsF7 correctly models the two-step hold sequence and uses backdated timing rather than real sleeps — consistent with the test seam rules in CLAUDE.md.
  • testMissionControlLeaderMEmitsCtrlUp correctly updated to include vimNavigation now that MC is purely additive.
  • testNoTwoCollectionsMapTheSameInputInTheSameLayer sensibly allows same-output duplicates (two collections agreeing isn't a bug) and skips chord inputs — both are the right calls.
  • withIsEnabled(true) map in testEveryCatalogCollectionEnabledAtOnce is a clean way to exercise all collections without mutating catalog state.
  • docs/gallery/pack-migration-plan.md update is tidy — marks the three nested-layer packs as shipped and removes them from the open work table.

@malpern malpern merged commit 7fcbdfb into master Apr 24, 2026
3 checks passed
@malpern malpern deleted the feat/m1-pack-fun-layer-and-collision-tests branch April 24, 2026 19:47
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