Resize handles live inside the scaled bg-image (~4.8×) / grid (~0.35×) layers,
but --duckmage-cal-scale only cancelled the viewport zoom, so image handles
rendered ~5× too large and grid handles too small. updateCalHandleScale() now
sets the var per layer to 1/(zoom × layerScale) so handles stay a constant
on-screen size at any zoom/scale.
Calibration drag/resize/arrow-nudge was paint-bound at ~2fps: moving or scaling
the grid re-rasterises all 3843 clip-path hex cells every frame (a probe showed
~0.05ms JS work vs ~540ms frame gaps). During an active adjustment we now swap
the cells for a single pre-drawn <canvas> colour snapshot (buildCalibrationSnapshot)
— transforming one canvas is a single composite op — and drop icons/labels/the
SVG outline. Mouse drags coalesce moves to one rAF tick and tear down on mouseup;
arrow-key nudges use a bake-on-quiet settle timer (no mouseup to end on).
Adds a dev-gated drag probe (window.DUCKMAGE_PERF) and dev/cal-handle-size-check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Map grid: the per-hex render loop called getTerrainFromFile +
getIconOverrideFromFile + getGmIconsFromFile, each re-running
getAbstractFileByPath + getFileCache — 3 identical cache lookups per hex
(~11.5k redundant ops on a 3843-hex map). Fetch frontmatter once and feed
three new pure extractors (terrainFromFm/iconOverrideFromFm/gmIconsFromFm).
Reuse the bg-image layer across renders (bgLayerEl/bgLayerSrc) so
renderGrid()'s viewportEl.empty() no longer re-decodes the image each time.
Tag painted hexes with .duckmage-hex-painted (setHexColor) and switch the
bg-image empty-hex rule to a class selector instead of a slower
:not([style*="background-color"]) attribute-substring match.
Table view: memoise the name→entry palette Map per region instead of
rebuilding it per row, and re-filter only the rows just filled in each batch
(applyFilters(rows?)) instead of re-scanning all rows after every batch.
Adds dev/bg-hex-recalc-bench + unit tests for the new pure extractors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hex-editor / map-link combo dropdowns lived inside the scrolling
`.modal-content` and were positioned `absolute`, so to avoid being
clipped the code toggled the scroll container to `overflow-y: visible`
while open. Switching a *scrolled* element from `overflow: auto` to
`visible` forces its scrollTop to 0 — so the first click on a section
you'd scrolled down to (Towns/Quests/etc.) snapped the whole modal to
the top. Browser-universal, not Linux-only (issue #31).
Replace the overflow hack with a shared `HexmakerModal.anchorDropdown`
helper that positions the dropdown `position: fixed`, anchored to its
trigger from JS. A fixed child resolves its containing block to the
viewport (no modal ancestor has a transform), so it escapes every
overflow clip without touching the scroll container — scroll position
is never disturbed. The helper repositions on scroll/resize/filter and
flips above the input when there's no room below. Both HexEditorModal
and MapLinkModal use it.
Also remove the dead `renderLinkSection` method and its now-orphaned
imports from HexEditorModal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wheel zoom: delta-aware (exp(-deltaY * k)) + rAF-batched so multiple
events per frame collapse to one paint. No inertia tail — the visual
lands exactly where the cumulative wheel deltas placed it and stops
the frame after the last event. Bake-on-quiet at 80ms so coords
un-blur fast without firing mid-stream.
Coord labels now render in .duckmage-coord-labels-layer at z:10 (above
path SVG, faction, region, GM-icon overlays) instead of inside each
hex. Positioned by percentage of gridContainer dimensions so they
auto-track through bake-zoom. Inline .duckmage-hex-label stays in DOM
hidden as insertBefore anchor for hex-icon ordering.
Settings: new "Coordinate label" row with size slider + font dropdown
(Interface/Serif/Monospace) + color picker (defaults to white). Hex
cell spacing slider now shows its current value and refreshes open
maps live.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same approach used for coord labels: render region names as HTML
divs inside gridContainer, positioned as percentages of the grid's
pixel dimensions. The PCA-derived rotation angle is preserved as
a CSS transform composed after the centering translate so labels
still align with each region's longest axis. Because the labels
ride the em-based hex layout instead of pixel-pinned SVG text,
they no longer slip during a bake-zoom transition.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After the SVG coord-label rendering was removed in 3cc956e, the
top/middle/bottom placement setting silently stopped working — the
HTML `.duckmage-hex-label` spans were just flex-centered inside the
hex, ignoring the setting.
Restored honour via CSS:
- `.duckmage-hex-label` is now `position: absolute; left: 50%;
transform: translate(-50%, -50%)` so the parent's flex centering
doesn't pin it. `top` is set per-placement class.
- `renderGrid` writes a `duckmage-coord-{top,middle,bottom}` class on
the viewport, derived from `settings.coordPlacement`. Three CSS
rules anchor the label at top: 22% / 50% / 78% (matching the
previous SVG ±0.28 hex-height offsets so visual position is
unchanged for users who never touched the setting).
- Default (when no class set) renders at top: 78% so an
uninitialized state still looks like "bottom" — the same fallback
the dropdown UI defaults to.
Coord-slip regression test in
frontend-testing/examples/coord-slip-on-zoom still PASSes: 0 slips
in the NEW bake mid-frame.
The previous fix (tear-down SVG before font-size mutation in bakeZoom)
addressed one path but the user kept seeing labels "slip a few hexes
away on zoom." More aggressive fix here: stop rendering the SVG copies
entirely, rely solely on the HTML `.duckmage-hex-label` spans that
live inside each hex's DOM.
The SVG coord-label copies were rendered into the path overlay
specifically so they'd sit above road/river strokes regardless of CSS
stacking. The trade-off was that their pixel positions were computed
once at SVG-creation time against the THEN-current hex sizes. Once
the user zoomed and bake mutated the viewport font-size, every
em-based hex dimension grew — but the SVG label coordinates didn't.
Any paint frame between the font-size mutation and the SVG rebuild
showed the labels stranded at the OLD smaller hex positions →
"sliding several hexes away" → SVG rebuild → "snap back." The
"snap back" was the noticeable artifact users described.
Removing the SVG copies eliminates the entire race. The HTML labels
ARE part of the hex DOM (`<span>` child of the hex `<div>`), so they
track the hex layout perfectly across any font-size change — no
slip possible. The CSS rule that hid HTML labels while SVG-mode was
on is also dropped; HTML labels are always visible now.
Cost: a road or river stroke drawn through a hex can visually obscure
that hex's coord label. Acceptable vs. the slip — and most maps have
sparse paths.
Regression test moved into the shared frontend-testing suite at
examples/coord-slip-on-zoom/. Two-half assertion (same pattern as
hex-token-alignment):
1. "OLD" bake order MUST reproduce the slip at the mid-transition
frame (else the test isn't exercising the bug). Currently shows
39 slips with one hex drifted by 99 px — exactly what the user
described.
2. "NEW" bake order MUST have zero slips at the mid-transition
frame.
Both halves pass.
A hex can now carry multiple GM icons. The HexEditorModal picker uses
left-click to add and right-click to remove individual icons, with a
"×N" badge on each tile when an icon is stacked. On the map, the icons
lay out in a hex-subgrid arrangement (reusing the same
tokenGroupOffsets helper the token layer uses, so multi-icon layout
stays visually consistent across the two systems).
Storage:
- New frontmatter key `gm-icons: [name, name, ...]` (array, duplicates
preserved for count).
- Legacy `gm-icon: name` (singular) still READ for back-compat —
existing notes Just Work. Writers always emit the new array key AND
clean up the legacy singular at the same time, so a note touched by
the new model standardises on the new shape.
Helpers (src/frontmatter.ts):
- New `getGmIconsFromFile(app, path): string[]` — authoritative reader.
- New `setGmIconsInFile(app, path, icons: string[])` — list writer.
- `getGmIconFromFile` / `setGmIconInFile` kept as thin compat wrappers
on top of the list helpers so the on-map painter and any future
caller that wants single-set semantics still has them.
HexEditorModal:
- `renderGmIconCountGrid` replaces the prior single-pick grid for GM
icons. Left-click increments, right-click decrements (clamped at 0),
the "— clear all —" tile wipes the list. Each tile shows ✓ for 1 and
×N for 2+. Writes the full list to frontmatter via `setGmIconsInFile`.
- Authoritative read via `getGmIconsFromFile`, keeping `directGmIcon`
populated as the first entry for the rest of the modal code that
still uses the singular field.
HexMapView:
- `data-gm-icon` (single) becomes `data-gm-icons` (JSON-encoded array)
on each hex element. New `parseGmIconsDataset` helper decodes it.
- `renderPathOverlay` GM-icon section: iterates unique icons in
first-seen order; positions each via `tokenGroupOffsets(n)` so they
cluster nicely in a sub-hex pattern. Icon size shrinks with slot
count (38%→22%) to keep them inside the hex. "×N" SVG text badge in
the lower-right when count > 1.
- Painter (paintIconGmOnly mode) still SETs to a single-icon list —
same overwriting behavior as before; multi-add is editor-only.
Painter undo records the FIRST prior icon (acceptable fidelity loss
for the painter's single-set semantics).
Styles:
- New `.duckmage-svg-gm-icon-count` for the on-map ×N badge (white
fill, stroked black for legibility over any terrain).
- New `.duckmage-icon-count-badge` for the picker tile badge.
Validation: 351 unit tests + 18 e2e pass. Manual UX test pending.
Five separate categories the upstream obsidianmd reviewer flagged on
the 1.4.x diff, plus enforcement to catch them earlier.
- **no-static-styles-assignment** (5 sites in HexMapView.ts):
Replace every direct `transform`/`transform-origin`/`opacity` write
with CSS custom properties on the relevant container. Static rules
in styles.css now reference them via
`transform: translate(var(--duckmage-bg-tx, 0px), …) scale(...)`
with identity-default fallbacks. JS writes only the values via
`setCssProps({ "--duckmage-bg-tx": ... })`. Applies to the bg image
layer, the hex grid container, AND the viewport itself. Two small
helpers `applyBgLayerVars` / `applyGridLayerVars` centralise the
variable plumbing and replace ad-hoc transform-string assembly at
the drag / wheel / arrow-key / bake / shiftForGridGrowth call sites.
- **document → activeDocument** (4 sites in overlayPatternControls.ts):
swap for popout-window compatibility.
- **setDynamicTooltip removal** (5 sites): the API is deprecated; the
value is shown inline now.
- **Lint enforcement**: `obsidianmd/no-static-styles-assignment`
promoted from default to error, so a regression breaks the local
lint instead of waiting for the upstream reviewer. The other two
patterns aren't shipped as eslint rules upstream — documented in
CLAUDE.md under "Obsidian reviewer-bot conventions" so the next
pass-through audits them by hand.
Sandbox regression check (dev/snapshot-bg-hex-alignment.mjs) still
PASSes — the CSS-var refactor doesn't disturb the bg-image / grid
alignment math from 1.4.1.
Per-map background image with full interactive calibration — pick or
drag-drop an image from the OS, drop straight into calibration mode,
align the hex grid to features in the image.
Calibration UX:
- Drag image body or grid body to move; corner/edge handles to resize
(image is aspect-locked; grid has top/bottom edges for hex-aspect
stretch). Anchor math: the side you grab moves, the opposite stays
fixed — see topics/design/notes/resize-handle-anchor-rule in the KB
for the derivation.
- Click an element to "focus" it; arrow keys nudge by 1 px (Shift = 10).
Sequential nudges coalesce into one undo entry.
- Floating ✓ button bottom-right commits, Esc cancels. Wheel falls
through to viewport zoom (precision alignment).
- Hex outlines drawn as an SVG overlay during calibration (CSS `border`
and `drop-shadow` filter approaches both fail on clip-path-shaped
hexes — the SVG overlay was chosen after sandbox iteration; see
dev/hex-calibration-sandbox.html).
- Resize handles use `transform: scale(1/zoom)` so they stay constant
screen size regardless of viewport zoom.
- Full undo/redo for every calibration mutation (drag, resize, nudge);
Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z hotkeys wired at view scope.
Persistence + alignment:
- New MapData.savedViewport persists per-map zoom/pan/font-size across
view close, so calibration values (sized to a specific font-size)
don't drift when the view reopens.
- bakeZoom now scales calibration values by the bake factor so
calibrated images stay aligned with the hex grid after wheel zoom.
Suppressed entirely during calibration (and pending bake timers
cancelled on entry) so the SVG outline doesn't drift mid-session.
- Path / faction / region overlay SVGs moved inside the gridContainer
so they inherit the gridDisplay transform — previously they sat as
siblings of the grid and drew at un-scaled coordinates, producing
the "two grids" effect on calibrated maps.
Other UX:
- Top/left grid expand buttons now visually anchor — the existing grid
stays put and the new row/column appears above/left, instead of the
whole grid shifting down/right.
- New "Background image" section in both the MapModal Properties tab
and the New map tab (with drop zone).
- Drag-and-drop OS images import to {hexFolder}/{mapName}/_bg/, with
filename collision auto-rename.
- "No terrain" hexes become transparent when a bg image is set, so the
image shows through.
- Calibration banner anchored to the view's contentEl (was body) so it
can't survive across view-close; defensive sweep on open clears any
banner left by a prior buggy build.
Dev infrastructure:
- dev/hex-calibration-sandbox.html — minimal hex grid that mirrors the
plugin's CSS, used to iterate on calibration outline / overlay
alignment approaches outside the live editor.
- dev/screenshot-approaches.mjs + snapshot-calibration-outline.mjs —
runners against the shared frontend-testing tool that produce one
PNG per CSS variant for LLM-direct visual review (see the
frontend-testing-tools KB project).
Bumps the faction-legend swatch and shrinks the pattern tile so the
pattern is legible at swatch-size rather than showing just 1-2 tile
repeats.
- On-screen legend: hex 28 → 32 px, pattern tile scaled by swatch/96
with a 4 px floor — yields ~6 repeats across the swatch.
- PNG legend: swatch fontSize*1.1 → fontSize*1.7, lineHeight floored
at swatch+4 so rows don't overlap; pattern shrunk by the same ratio.
- renderHexPreview gains an optional patternScaleMultiplier param so
callers (legend, palette, future small-swatch contexts) can shrink
the pattern without touching the user's scale slider value.
Also bundles the prior 1.3.0 follow-up fixes (hex-shaped on-map-scale
preview, palette tile refresh-on-save, thin outline default + slider)
since those were committed locally on top of 1.3.0.
Three refinements to the 1.3.0 overlay pattern editor based on user
feedback:
- Preview: was a 120×36 rect at the pattern's own scale. Now a hex
polygon at the live on-map hex size (read from .duckmage-hex,
falling back to 96 px), in the configured orientation — so the
preview shows exactly what the overlay will look like on the map.
- Palette tiles: were never updating on Save because the picker
re-read style from the metadata cache, which lags Obsidian's
frontmatter write by a tick. The editor's onSaved callback now
passes the new OverlayStyle directly, so the picker renders from
the editor's in-memory state and updates immediately.
- Outline width: was hardcoded to gapPx*2+2 (often 14-22 px), which
made overlay borders read as solid blobs. Now per-overlay,
defaulting to 1.5 px, with a slider (0-20, step 0.5) in the editor.
Persisted as faction-outline-width / region-outline-width
frontmatter keys. Crank it up to recover the old thick-blob look.
Adds per-overlay pattern fills so factions and regions can be visually
distinguished when they overlap (issue #25). Each faction note and each
region note now carries a pattern, scale, and opacity in its
frontmatter, edited via the existing FactionEditor / GeoRegionEditor
modals (dropdown + sliders + live preview swatch).
Pattern set (10 total): solid (the existing flat fill, default),
diagonal stripes ↗ / ↖, crosshatch, polka dots, grid, zigzag,
triangles, scales, checker.
On screen: SVG <pattern> defs are deduped per (key,color,scale) and
referenced from each overlay path's fill. Stroke stays solid color so
blob boundaries remain crisp. Per-overlay opacity replaces the
hardcoded 0.45 group opacity.
PNG export: mirrored via Canvas patterns built from an offscreen tile
per (key,color,scale), cached for the render pass.
Backwards compatible — overlays without the new frontmatter keys fall
back to solid + 0.45 opacity, matching pre-1.3.0 behavior.
- Sentence-case: rephrase "GM" → "Game master"; remove disable comments;
autofixer lowercases "🎲 Open tables view" / "← Back" to comply
- no-static-styles-assignment: dynamic colors now use CSS custom properties
(--duckmage-bg / --duckmage-border-color / --duckmage-fg) consumed by
existing classes; static positions moved to small modifier classes
(duckmage-rt-roll-btn-spaced, duckmage-wf-steps-list, etc.); terrain
toolbar uses is-terrain-preview / is-eyedropper-active toggles;
createIconEl uses new duckmage-masked-icon class
- Popout-window compat: setTimeout / clearTimeout / requestAnimationFrame
→ window.X; document.X → activeDocument.X across all view/modal files
- Unnecessary `as ArrayBuffer`: replace `data.buffer.slice(...) as ArrayBuffer`
with `new ArrayBuffer + Uint8Array.set` — gets a real ArrayBuffer without
the assertion (also avoids ArrayBufferLike narrowing)
- Deprecated SettingTab.display(): extract body into private renderSettings();
display() and post-action refresh both call it
- eslint.config: add activeDocument + activeWindow to globals so the renamed
identifiers don't trip no-undef
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a unified export pipeline under src/export/ with exporters for single notes,
structured hexes, maps (with reference table), random tables, and workflows
(with rolled samples). PDF rendering reuses Obsidian's MarkdownRenderer + theme
CSS; map PNG/PDF goes through a dedicated canvas renderer.
UI surface:
- "Export" tab in MapModal (PNG/PDF with overlay + size options)
- "Export" link in HexEditorModal + HexExportModal
- "Export PDF/Markdown" links in RandomTableView header
- "Export…" button in WorkflowEditorModal + WorkflowExportModal
- Commands: export current note (PDF/MD), current hex, current workflow
- File-menu items on any .md file: Export to PDF / Markdown
Other:
- New `exportFolder` setting (defaults to {worldFolder}/exports)
- hexGeometry: add hexSize / hexCenter / hexPolygonPoints / gridBoundingBox
- HexTableView + HexEditorModal: first-wins on duplicate palette names
(matches HexMapView's .find() behaviour)
- Tests: unit + integration suites for all exporters; sim-vault fixture
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat: tabbed Maps modal; faction hover tooltip; map starting coordinates; default map/submap sizes in settings
- Maps modal refactored into three tabs: Maps (switch/delete), Properties (rename, terrain theme), New map (labeled fields, size presets)
- Faction overlay hover shows each faction on its own line with color swatch via custom floating tooltip div
- New map and submap creation accept starting X/Y coordinates; hex filenames and labels both reflect the chosen origin (gridOffset initialized accordingly)
- Map switching via modal now goes through switchToMap so viewport save/restore applies correctly
- Added defaultNewMapCols/Rows and defaultSubmapCols/Rows settings; new-map forms pre-fill from these
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Undo/redo now covers all paint tool types: terrain (existing), icon
override, faction link paint/erase, and region paint/erase. Each
stroke is committed on mouseup using the same Map-per-stroke pattern
as terrain.
- Fix: undoing an icon override now restores the underlying terrain icon
(previously left the hex blank).
- Fix: "Remove icon override" context menu item now calls renderGrid so
the icon disappears immediately without a reload.
- Multiple tokens on the same hex now spread out using preset offset
patterns (2=left/right, 3=triangle, 4=2×2, 5=pentagon, 6+=ring) so
all tokens are visible with slight overlap. Spread radius scales with
hex size.
- Hovered token rises above stacked siblings via z-index on :hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- folder tree view in link-table modal (collapsed by default, filter expands all)
- shared renderFolderTree utility; RandomTableView refactored to use it
- submap picker: filterable list replaces combo-dropdown; terrain theme swatch per map
- map terrain theme: per-map terrainType field drives submap center-dot color and swatch in switch-map list
- erase mode for paths/factions/regions: "Remove" tile + enters erase immediately
- submap link tool: blip animation on hex after linking
- hex map view header updates to active map name on switch
- fix: link-table modal scopes folder tree to tablesFolder only (was leaking vault-wide folders)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single right-click while any drawing tool is active now opens a context
menu with "Switch <toolname>" (where applicable) and "Exit tool", replacing
the old double-right-click-to-exit gesture and the "↩ map mode" toolbar
button that caused layout shift (fixes#17).
New PainterContextMenu class is designed for subclass extension via
extraOptions() — individual tools can inject additional items later.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents MapLinkModal and SubmapPickerModal from being too short to show
the combo dropdown without triggering a scroll.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
position:fixed failed because Obsidian's modal CSS animation sets transform on
.modal, making fixed children position relative to it (not the viewport).
Instead, toggle .duckmage-combo-open on .modal-content (overflow-y:visible)
while the dropdown is open so the absolute-positioned dropdown doesn't trigger
the scroll container.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Combo dropdown now uses position:fixed with JS-calculated viewport coords so it
never causes the modal's overflow-y:auto container to scroll
- MapLinkModal: map filter and link text both start blank; link text auto-fills
on map selection only if still empty
- SubmapPickerModal: focus opens all maps (blank query)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Right-click hex → "Link submap" writes duckmage-submap frontmatter
- Hex flower shows centre dot (hidden when no link) that navigates into the submap
- Centre dot centering is orientation-aware (flat-top top:24px, pointy-top top:20px)
- ← Back button appears next to map dropdown (via flex nav group) after submap drill-in
- "Insert map link" editor context menu inserts obsidian://duckmage-openmap URI
- Map rename updates all duckmage-submap references via updateSubmapReferences()
- HexEditorOptions refactored: onNavigate/onModalClose/onSwitchMap in options object
- 10 new tests for submap frontmatter helpers and rename propagation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tokens placed per-hex link to vault notes; support circle/square shape,
sm/md/lg size, fill color, optional border color, icon, and description
- TokenInfoModal shows note content (formatted via MarkdownRenderer, images
included) with jump-to-hex, open-note, edit, and two-step remove actions
- Edit flow reopens TokenModal pre-filled; proxy notes handle multiple tokens
referencing the same source note
- Icon picker replaced with scrollable palette grid (2-row cap, drag-to-reorder,
hide/show) in both HexEditorModal and TokenModal; order persisted in settings
- Token layer toggle added to layers menu (default on)
- Fix metadata-cache race on token delete (wasToken check prevents missed updates)
- Token preview rendered inline in TokenModal title row; real-time color preview
via native input event on color pickers
- Bump version to 1.1.6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix map pixelation on zoom: bake CSS scale() into viewport font-size after
scroll wheel settles so the DOM renders natively at the zoomed resolution
instead of upscaling a compositor bitmap; redraw SVG overlays after bake
- Fix terrain brush size persisting when switching to icon tool: reset
paintBrushSize to 1 on icon mode entry
- Add shrink (−) buttons paired with each expand (+) button: fade in on hover,
positioned to the right for top/bottom and below for left/right; clicking a
clean row/col shrinks immediately and trashes the hex notes; clicking a dirty
row/col shows a red OK? confirm state (3 s timeout) before deleting
- Fix stale terrain showing after delete+re-expand: pass null terrain/icon
overrides for newly-added edge hexes so cached frontmatter from deleted files
cannot bleed through
- Add iconsFolder to onboarding wizard folder generation step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace hover-fade "Delete" button with a fixed 20×20px ✕ button always
visible in each map row
- Hide the ✕ entirely when only one map remains (deletion blocked)
- Remove red background from confirmation row; use muted text instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each map row in the switch-map list now shows a "Delete" button (visible on
hover). Clicking it enters an inline confirmation state showing the map name
and a warning that hex notes will be trashed. Confirming calls
fileManager.trashFile() on the hex folder (if it exists), removes the map
entry from settings, reassigns defaultMap/activeMapName to the next available
map, saves, and re-renders. Cancelling returns to normal without changes.
Fixes#3 — map entries left dangling after manual folder deletion are now
cleanly removable through the UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New SetupWizardView (ItemView): 4-step welcome → folders → map → done
- Wizard auto-opens on first install (setupComplete/setupDismissed flags)
- Folder step creates all world subfolders and ensures hex template exists
- Map step creates the first hex grid; done step shows recommended next steps
- "Open hex map" from done step opens map with terrain picker + toolbar open
- Re-run wizard button added to settings tab
- Fix: loadAvailableIcons() now runs inside onLayoutReady (vault readiness)
- Fix: migrateHexFilesToDefaultRegion() no longer creates world/hexes/default
on fresh installs — scans candidates first, early-returns if none found
- Fix: loadHexTemplate() falls back to built-in default instead of returning null
- Fix: terrain toolbar button re-opens picker when clicked while active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>