chore(skills): rebuild skills index - #1
Open
github-actions[bot] wants to merge 1 commit into
Open
Conversation
DrOlu
pushed a commit
that referenced
this pull request
Jun 9, 2026
* feat(canvas): interactive ghost placement for new nodes (#237)
Replace immediate auto-placement of new canvas nodes with a "ghost" picker:
creating a node now shows ranked, translucent previews at smart candidate
spots and lets the user choose where it lands (click, number keys 1..N, or
Enter for the best spot; Esc / click-empty cancels).
- findFreePositions(): ranked candidate algorithm seeded from the focused node
and its nearest neighbours, with on-screen-first ranking and a guaranteed
fallback so crowded layouts never yield an empty list.
- canvasStore pendingPlacement state + beginPlacement/commitPlacement/
cancelPlacement/setPlacementHover actions.
- placePanel fork: ambiguous (position-less) creates defer to ghosts; explicit-
position paths (drag-drop, restore, right-click "new here") place as before.
Cancelled placements roll the orphan panel record back.
- GhostPlacementLayer component rendered in the canvas world div.
- Unit tests for the candidate algorithm and placement actions.
* feat(canvas): smarter ghost placement — no overlap, mouse-anchored, multi-size
Address review feedback on the ghost picker:
- No overlapping recommendations: candidates are now rejected if their rect
overlaps any existing node OR an already-accepted candidate (the prior version
only checked existing nodes, so adjacent ghosts could overlap each other).
- Anchor to the mouse: recommendations are generated as a ring around the last
canvas-space pointer position and ranked by proximity to it (falls back to the
focused node, then viewport centre). Canvas tracks the pointer rAF-throttled
via a non-reactive store setter.
- Zoom out to show recommendations: beginPlacement frames all ghosts (plus the
focused node) by zooming OUT only; commit restores the prior zoom and centres
the new node, cancel restores the full viewport.
- Multiple aspect ratios: placementSizeVariants offers Standard/Wide/Tall/Large
per panel type; candidates cycle variants for shape variety and each ghost
shows its size label. The committed node uses the chosen candidate's size.
findFreePositions is replaced by findPlacementCandidates (point+size+label).
Tests updated and extended (no-overlap, mouse anchor, variants, zoom-out/restore).
* feat(canvas): cursor-anchored, live-following ghost placement
Replace the fixed rosette of aspect-ratio ghosts with a cursor-driven cluster:
- placementCluster() builds a small cluster anchored at the cursor — a primary
"drop here" ghost centred on the mouse, with Wide/Tall/Large alternates tucked
beside it, each nudged to the nearest free spot so nothing overlaps a node or
another ghost.
- The cluster follows the mouse live: a full-bleed placement surface (above
nodes, below ghosts) tracks the cursor (rAF-throttled) and re-anchors the
cluster via updatePlacementCursor; clicking anywhere drops the primary, so the
whole canvas becomes "click to place here". Numbered ghosts / 1..N / Enter pick
a specific shape; Esc cancels.
- Drop the auto zoom-out — it fought cursor anchoring (moved recommendations
away from where you point). Placement now happens at the current zoom, right
under the cursor.
findPlacementCandidates (rays+ring rosette) is removed in favour of
placementCluster. Tests updated for the cursor-anchored model + updatePlacementCursor.
* feat(canvas): recommendation picker — zoom out + 3-5 numbered ghosts
Settle on a single creation model: trigger a create → the canvas zooms out and
shows 3–5 recommended spots as numbered ghosts; the user picks by clicking one
or pressing its number (Enter = best, Esc / click-empty cancels).
- recommendPlacements(): up to 5 ranked, non-overlapping recommended positions
(rays past obstacles from the focused node + its neighbours, plus a ring near
the anchor), all at the panel's default size. Ranked by proximity to the
anchor (mouse → focused node → viewport centre), on-screen first; never
overlaps a node or another recommendation.
- Zoom out to frame all recommendations (+ focused node); restore the prior
zoom on commit (then centre the new node) and the full viewport on cancel.
- Remove the cursor-following cluster + placement surface + updatePlacementCursor
and the per-ghost aspect-ratio labels; ghosts are now clean numbered targets.
* feat(canvas): polish picker, add click-anywhere escape hatch + toolbar drag
Three improvements to new-terminal creation:
1. Escape hatch — "what if no recommendation fits?": the dimmed placement
surface is now clickable. Hovering empty canvas previews a free spot ("Place
here"), and clicking anywhere drops the terminal there (nudged to the nearest
non-overlapping grid spot). New store bits: pendingPlacement.freeGhost,
updatePlacementCursor, commitFreePlacement, and the nudgeToFree helper.
2. Picker polish: a dim scrim focuses attention, numbered ghosts animate in with
a stagger + hover glow and a "Best" tag on #1, and a fixed hint pill (portal)
explains the controls (1–N / click / Esc) with a Cancel button.
3. Toolbar drag-to-place: dragging the toolbar terminal icon onto the canvas
spawns a ghost that follows the cursor; releasing drops a terminal at that
exact spot (explicit position → bypasses the picker). A plain click still
opens the recommendation picker.
Tests added for nudgeToFree, updatePlacementCursor, and commitFreePlacement.
* feat(canvas): placement setting (auto vs picker), tidier recommendations, hint fix
- Setting (Canvas → "Recommend where new panels go", default ON): when enabled,
Cmd+T / toolbar click open the recommendation picker; when off, the panel is
auto-placed in the best spot immediately (no zoom-out, no ghosts). Gated in
placePanel via settingsStore. New AppSettings.placementPicker + store schema.
- Recommendations now align to the existing layout: candidates are the four
edge-adjacent slots of each node (aligned to its top/left edge, uniform gap),
kept only if they clear every node AND other ghosts by a full gap — no more
touching ghosts or oversized gaps. Replaces the ray/ring generator.
- Hint pill moved to top-centre so it no longer overlaps the bottom toolbar.
* feat(canvas): context-aware placement recommendations
Recommendations now adapt to what the user is focused on instead of scattering
spots around every node:
- Active node (the focused node when on screen, or a single node dominating the
viewport when zoomed in) → recommend tightly AROUND that node: its four
directions, hopping just past an immediate neighbour when an edge is blocked.
The active node is the centre of the recommendations.
- No active node, viewport blank (panned to empty space) → recommend centred on
where the user is looking.
- No active node, nodes on screen → group nodes into islands (union-find by
edge-gap), pick the island nearest the anchor (cursor/viewport centre), and
recommend around its perimeter — more, further-flung spots.
- Empty canvas → a few spots centred on the anchor.
Adds geometry helpers (rectGap, intersectionArea, pointRectDistance,
clusterNodes, boundsOf). Tests cover each mode.
* feat(canvas): contiguous active-node recs, F-gated free placement, focused picker chrome
- Active-node recommendations now form ONE connected group next to the node via
an edge-anchored lattice + BFS that only accepts free cells and never crosses
an occupied one — so in a crowded cluster the ghosts stay attached to the
node's free corner instead of scattering to opposite cluster edges.
- Free "place anywhere" is gated behind F: the "Place here" ghost no longer
follows the cursor by default. Press F to arm it (crosshair + preview, click to
drop); otherwise clicking empty canvas cancels. New pending.freeArmed +
setFreeArmed.
- Picker chrome moved to screen space and focused: the hint pill is centred over
the canvas (not the window) with the em dash removed and an F hint added; an
app-wide dim with a bright cutout over the canvas + its tab bar recedes the
sidebar/chrome while the canvas itself stays bright (transparent surface).
Tests: active-node cluster connectivity + F-arming lifecycle.
* feat(canvas): gap-fill recommendations with custom sizes
Sub-standard gaps between nodes now get their own recommendation, sized to fit
instead of being skipped:
- gapFillCandidates finds horizontal/vertical gaps between a cluster's nodes that
are too narrow/short for a standard panel but at least PLACEMENT_MIN_W/H and
within PLACEMENT_MIN_AR..MAX_AR aspect ratio, and emits a custom-sized rect
centred in each (keeping the uniform gap to its neighbours).
- recommendPlacements now works on per-candidate rects (point + size), so
standard and custom-sized ghosts mix freely; custom carry a small ranking
penalty so a comparable standard spot still leads. Runs within the active
node's cluster or the nearest island.
Tests: a sub-standard gap yields a custom ghost in the gap; a gap below the
minimum yields none.
* fix(canvas): dim sidebars + centre hint on the VISIBLE canvas
The canvas spans full-width behind the absolute-overlay sidebars, so the picker
overlay's canvas rect was the whole window — the hint landed at window-centre and
the dim box-shadow had nothing outside the window to darken.
Mark the sidebars (data-app-sidebar="left|right") and compute the visible canvas
strip between them: the dim cutout now actually darkens the sidebars (canvas + its
tab bar stay bright), and the hint pill is centred on the visible canvas, matching
the bottom toolbar.
* feat(canvas): adaptive recommendation size + fill gaps of any size
Recommendations now match the panels they sit beside instead of a fixed default:
- Adaptive size: in active mode the new panel matches the active node's size; in
island mode it matches the cluster's median panel size — clamped to the
placement min/max (280×180 … 1400×900) and aspect-ratio bounds. So in a layout
of large terminals the ghosts are large too.
- Gap-fill now sizes a panel to FILL each between-node gap (clampPlacementSize),
covering both narrower- AND wider-than-reference gaps, and emits it only when
it differs from a reference-sized spot. Custom penalty lowered so these surface.
- recommendPlacements restructured: context (active/island/blank/empty) and the
relevant cluster are resolved up front, then the adaptive size is derived.
Tests: active node size is matched; gap-fill positive/negative unchanged.
* feat(canvas): dim+block all app chrome during placement (except canvas tab)
The picker is now a true modal: everything outside the canvas is dimmed AND
non-interactive until a spot is placed or aborted.
- The dim is built from interactive rectangles (not a box-shadow) covering the
whole window except the canvas content and its tab — so the header bar dims too
while the active "Canvas" tab stays bright. Clicking any dimmed area cancels.
- In-canvas chrome (toolbar, minimap; tagged data-canvas-chrome) is made inert
via a body.cate-placement-active class while a placement is pending. Existing
nodes are already covered by the in-canvas surface.
- A transparent blocker keeps the bright canvas tab inert too.
* feat(canvas): fill irregular gaps via maximal empty rectangles
Gap-fill now detects the actual holes in a layout — including the irregular ones
a staggered arrangement leaves — instead of only gaps between two aligned nodes:
- findEmptyRects: coordinate-compress node edges within the cluster bounds into a
free-cell grid and extract the maximal empty rectangles.
- gapFillCandidates sizes a panel to fill each hole (clampPlacementSize), emitted
only when it differs from a reference-sized spot.
- A gap-filler now WINS its hole over the overlapping reference spot (small
ranking preference), capped at ~half the slots so standards still show — so the
individually-sized ghosts actually appear instead of being overlap-rejected.
Test: a staggered two-node layout yields a custom ghost filling an irregular hole.
* refactor(canvas): drop the placement dim/block overlay
The picker no longer dims or freezes the rest of the app. The free "place
anywhere" surface only mounts while free mode is armed (F); otherwise the app
stays fully interactive and you pick a ghost directly (click / number / Enter),
Esc or an empty-canvas click cancels. Removes the full-window dim rectangles,
the canvas-tab cutout, the chrome-disable body class + CSS, and the
data-canvas-chrome markers. PlacementOverlay shrinks to a small PlacementHint.
* refactor(canvas): centralize placement algorithm in placement.ts
Move the whole recommendation algorithm — recommendPlacements, findFreePosition,
nudgeToFree, the geometry helpers (clustering, empty-rectangle finding, gap-fill,
clamping) and the placement constants/types — out of the 2k-line canvas store
into one focused, pure, dependency-free module (canvas/placement.ts). The store
now just imports it and owns the picker state/actions; rendering lives in
GhostPlacementLayer. Drops the dead placementSizeVariants/PlacementSizeVariant.
No behaviour change; tests updated to import from the new module.
* fix(canvas): recommend the standard panel size around the active node
Recommendations now always use the standard default panel size instead of
mimicking the active node's (possibly unusual) shape — a new terminal next to a
tall/narrow window should still be a normal terminal. Only gap-fill candidates
take a custom size, to fit an irregular hole. Drops the now-unused adaptive
sizing + medianNodeSize helper.
* fix(canvas): fill big gaps beyond the cluster threshold with custom ghosts
Recommendations prefer the standard panel size, but individualize for holes a
standard panel doesn't fit — including BIG gaps. Gap-fill previously ran only
within the focus node's tight cluster, so two nodes farther apart than the
clustering threshold left their (large) gap unfilled. It now scans the focus
plus every node within ~2 panels of it, so a wide gap between far-apart nodes
gets a larger custom ghost that fills it.
Test: a wide gap between two tall nodes yields a larger-than-standard ghost.
* fix(canvas): fill more spots around the active node
The active-node lattice generated only 4 standard spots while the cap was 6, so
when no gap-fill custom filled the other two slots only 4 ghosts showed and the
free space around the node looked half-used. It now generates plenty of free
spots (nearest-first) and the accept pass keeps the closest `cap` of them — so
the recommendations fill the space around the active node (up to 6), still
hugging it (best spot is adjacent), with gap-fill customs where a standard panel
doesn't fit.
* refactor(canvas): rewrite recommendations as one minimal empty-rect pass
Replace the accreted machinery (clustering, islands, median sizing, lattice-BFS,
separate gap-fill, custom-preference ranking) with one clean idea:
- Find the ACTIVE node (focused if on screen, else the on-screen node nearest the
viewport centre) — the "when active" logic stays separate.
- Look at the empty rectangles around it (findEmptyRects) and drop ONE panel into
each: standard size where it fits, shrunk to fill the hole otherwise
(individualized), hugged against the active node (aligned to its top-left,
clamped into the rect).
- Empty canvas / panned-to-blank → a few standard spots where the user is looking.
- finalize() ranks nearest-first (on-screen first), dedupes, and accepts only
non-overlapping spots.
placement.ts drops from 567 to 373 lines; removes rectGap, intersectionArea,
pointRectDistance, clusterNodes, boundsOf, gapFillCandidates, medianNodeSize.
* fix(canvas): hug the active node with a lattice instead of scattering
The empty-rectangle pass treated every nearby hole equally, so for a node inside
a dense grid the ghosts scattered across the whole cluster's perimeter — far from
the active node. Replace it with a lattice anchored on the active node's edges:
BFS outward (nearest-first) through free cells, each panel sized to fit its cell
(shrunk when a neighbour crowds it — individualized). Spots now hug the active
node and extend its grid instead of spreading. Removes findEmptyRects.
* fix(canvas): rebuild placement recs as a place-area grid scan
The old picker anchored a lattice to the active node and flooded outward
through free cells only — so a node boxed in on all four sides emptied the
queue and fell back to a single spot, and large open areas got one sparse
ghost. It also never distinguished a focused node from an unfocused canvas.
Replace the BFS with a full bounded scan of a panel-pitch lattice over a
place area:
- Focused node on screen → a tight area around it; recs hug the node and,
because the scan visits every cell (not just reachable free ones), still
reach the open space just beyond a surrounding ring.
- Nothing focused → the whole viewport, lattice phased to the on-screen node
nearest the cursor → a wider, island-biased spread with more options.
- Pitch-spaced cells tile the free space densely without overlapping, and
fitAt keeps the standard size where it fits, shrinking only to fill a
tighter cell (individualized sizes, standard preferred).
Add tests for the boxed-in case (now ≥3 spots, not one fallback) and the
focus-vs-cursor differentiation.
* fix(canvas): fill gaps between staggered windows with a finer grid
The single node-phased lattice was too coarse: its cells aligned to one
reference node, so they landed in open areas or on top of a window, but
stepped right over the irregular gaps between staggered windows — and
nothing shrank, so every ghost was standard size.
Generate candidate positions from a grid woven from the reference lattice
(open areas) AND every nearby node's edges — the gap just right of / below
each window. fitAt then shrinks each spot to its actual gap, so smaller
holes get smaller, individually-sized ghosts (masonry) while roomy areas
still get standard panels. Holes below the minimum panel size stay empty.
* fix(canvas): keep the spot beside a node and stop far-flung recs
Two issues when a window was focused:
- No spot appeared directly beside the focused node. The hug anchor
(node.edge + gap) was grid-snapped with round(), so a non-grid-aligned
edge landed a few px too close — inside finalize's gap-1 clearance — and
every spot on the node's own rows got dropped. Snap hug anchors AWAY from
the node (ceil to the right/below, floor to the left/above) so the gap
survives the snap.
- A recommendation showed up far from the focused node. Tighten the focused
place area from 1.5 to 1 panel pitch so spots stay clustered around it
(still reaches one pitch past a surrounding ring for the boxed-in case).
Add a regression test for the non-grid-aligned hug edge.
* fix(canvas): hide shadowed spots and make the grid type-independent
Two refinements to the placement picker:
- Occlusion: don't recommend a spot that sits behind a nearer one. A
segment test from the ranking point (the active node, or the cursor)
through each already-accepted spot drops candidates you'd have to look
past another recommendation to reach.
- Consistent grid across panel types: lay spot POSITIONS on a canonical
slot (the largest panel default) instead of the panel's own size, so the
grid is the same whatever you're placing; each ghost is still sized to its
own type, shrinking only to fit a tight gap. (A bigger panel still fits
fewer spots in open areas — that part is geometric, not a bug.)
Add occlusion and per-type regression tests.
* fix(canvas): cluster focused recs near the node, no far gaps
Giant gaps came from two things: the canonical 800x600 slot made the grid
coarse and the place-area large, and a boxed-in focused node would scatter
spots into distant open space to fill the count of 6.
- Revert the oversized slot: lay the grid on the panel's own size again, so
it's tight. (Positions stay consistent across types anyway because the
hug ring is anchored to the node's edges, not the panel size.)
- Cap focused recs to within ~one panel-pitch of the node. An open node
still gets a full ring of 6; a boxed-in one shows the few genuinely-near
spots instead of far-flung ones.
Add a regression test that a node boxed on three sides keeps its recs near.
* fix(canvas): keep focused recs to one connected cluster touching the node
The previous one-pitch distance cap still let a spot land almost a full
panel-width away, which reads as a giant gap — especially a column hugging
some OTHER nearby window.
Replace the cap with a connectivity filter: seed with spots adjacent to the
focused node, then grow only through spots adjacent to each other (within ~one
gap). A spot that doesn't chain back to the node — because a neighbour window
sits between them — is dropped regardless of how near it ranks. Open nodes
still get a full ring; a blocked side yields nothing past the blocker rather
than a floater.
Add a regression test that nothing is recommended past a blocking neighbour.
* refactor(canvas): place recs by nearest-first free-space packing
The lattice + node-edge candidates + greedy non-overlap selection produced
odd gaps on non-grid layouts: it could leave empty space close to the node
unused while a spot landed farther out. No amount of capping/filtering fixed
the root cause, which was the candidate model itself.
Replace it with proper packing (MaxRects-style): decompose the free space
(place area minus the windows) into empty rectangles, then repeatedly drop one
ghost into the free spot CLOSEST to the ranking point, carve it (plus its gap)
out, and repeat. Because the nearest free space is always used first, no closer
empty spot is ever left unused — which is what removes the weird gaps — and the
result is mutually non-overlapping, tiles large areas, and shrinks a ghost to
fill a tight gap. Focused → packs around the node; unfocused → around the cursor.
Grid-snap each packed spot INTO its free rect so the gap survives snapping.
Drop the now-obsolete occlusion/connectivity machinery. Swap the stale occlusion
test for a packing-non-overlap test.
* refactor(canvas): dedupe free-rect pruning into one helper
* refactor(canvas): drop unused placement code
- PlacementCandidate.rank/onScreen were written but never read by the
renderer or store (the ghost layer uses the array index, and on-screen-ness
is only an internal ranking signal). Reduce the type to {point, size}; the
two tests that read those fields now derive on-screen from the viewport.
- Delete layoutEngine.findFreePosition — a stale, name-colliding copy with
zero callers (the live one lives in placement.ts).
github-actions
Bot
force-pushed
the
skills-index-update
branch
8 times, most recently
from
June 16, 2026 11:34
aa831c2 to
7eadccb
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
8 times, most recently
from
June 24, 2026 09:52
f716923 to
bcb7687
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
8 times, most recently
from
July 2, 2026 09:32
9588ad2 to
4e143db
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
5 times, most recently
from
July 7, 2026 10:01
69226d8 to
3407431
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
9 times, most recently
from
July 19, 2026 08:33
8245a64 to
1e2b75d
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
7 times, most recently
from
July 26, 2026 08:40
7a63daa to
2d2dd7e
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
8 times, most recently
from
August 3, 2026 10:06
e3ed8a7 to
48d521b
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
4 times, most recently
from
August 7, 2026 07:41
f432084 to
125a5ec
Compare
github-actions
Bot
force-pushed
the
skills-index-update
branch
from
August 8, 2026 07:14
125a5ec to
ce7be8a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated rebuild of
registry/skills-index.jsonfromregistry/sources.json.