Skip to content

Mundus v0.8.2

Choose a tag to compare

@NotBedd NotBedd released this 22 Oct 16:56
· 38 commits to main since this release

The app version jumped to v0.8 to signify the huge step done after publication

Below are the combined Worklogs of v0.8 , v0.8.1 and v0.8.2 (including fixes over previous implementations)

Mundus v0.8 - Work Log

1. Map Mode — custom marker icons actually render now

Root cause found: createMarkerIconHtml() in MapMode.tsx received the chosen iconName but never used it — every marker was drawn as the same teardrop pin with an empty white disc, which is why the icon picker "didn't really do much".

Fix: the chosen icon is now rendered as a real glyph (lucide SVG via renderToStaticMarkup) centered on the pin's white disc (14×14 px at the disc's center, dark stroke for contrast on any pin color). The default MapPin selection keeps the clean standard-pin look exactly as before. All icon sources work: the icon picked in the create/edit marker menu, category defaults, and icons inherited from Codex entries.

2. Harper suggests 3 corrections, cycle with arrows

  • lintToSuggestion() (grammarEngine.ts) now returns a choices list of up to 3 unique candidates (Harper's own precedence preserved; Remove-kind candidates map to "(delete)"; duplicates deduped).
  • While the suggestion popup is open, ← / → cycle through the candidates (editor caret movement is intercepted only while the popup is open, so normal navigation elsewhere is untouched). The popup shows ‹ › chevrons you can also click and a 1/3 counter.
  • Tab still confirms the currently shown candidate; Esc dismisses; "+ Add" whitelist unchanged. The dictionary fallback path also offers 3 choices now.

3. Sync status hover popup — new layout (expanded + collapsed variants)

Cloud Storage                0.02% of 1GB   ← percent keeps its dynamic color
[────────── bar ──────────]
This Mundus                        12.3 KB  ← single line, value on the right
All Mundus                          1.2 MB  ← single line, value on the right

The old bottom row crammed All Mundus: X + Quota: 1.0 GB together and wrapped; the quota moved up into the header (x% of 1GB), both bottom rows are now label-left/value-right and always fit on one line. Applied to both the expanded-sidebar tooltip and the collapsed-icon floating popup.

4. Highlighter: one-shot when applied to selected text

  • Selected text + color → the selection is painted, then the highlighter automatically disarms: the browser's typing background is cleared (scratch-collapse + hiliteColor transparent + selection restored) and a sync-guard prevents the toolbar state from re-latching from the just-painted text. Anything you type afterwards is not highlighted.
  • No selection (collapsed caret) + color → unchanged latching behavior: everything you type is highlighted until you toggle it off.

5. "BETA" badge removed from the Map button (sidebar)

Both the visible BETA chip and the title="Map (Beta)" tooltip are gone.

6. Renamed Codex categories now show in the "In This Scene" drawer

Entries store the section id (e.g. factions), but the drawer displayed the raw id — so renames never showed ("Factions" stuck forever). The drawer now receives the project's codexSections and displays the current label everywhere: header chips, entry badges (collapsed + expanded), category filter chips, and even search-by-category-name. Unknown/deleted ids fall back to the raw string like CodexMode does.

7. Scene Codex header cleaned up + Codex tab count

  • Removed the redundant "x codex entries detected in this scene" line under "Scene Codex".
  • The Codex tab now shows the number of detected codex entries (not characters): Codex (3) — matching the Characters (n) pattern of the tab next to it (tooltip explains the number).

8. Detection never matches inside words (xkylex / xkyle / kylex)

The drawer/editor/characters/map detectors were already whole-word aware, but the Codex entry form's "which documents mention this entry" list used raw substring matching (docText.includes(entryTitle)) — an entry titled "Kyle" was reported as mentioned by any document containing "xkylex". That logic is now a shared, whole-word helper getEntryDocumentMention() (sceneDetection.ts) built on the same Unicode-boundary matchesMention() engine: title in doc title → "In title", title in text → "Mentioned in text", subtitle (≥4 chars), tags (≥3 chars). Possessives ("Kyle's") still count; glued words never do.

9. Backups verified — deep/nested data round trip (test suite added)

New scripts/test_backup_verification.ts (38 checks) round-trips the REAL engine functions with a fixture containing every "deep" structure: character family tree (relatives), codex multi-image gallery (3 images incl. oversized data URLs), story flow link label + notes, story flow layouts, map markers with icons/colors, the full-res map image, document visual images, per-project calendars and renamed codex sections.

10. Android-only: long-press the Save button → Auto-Save dropdown

On Android (Capacitor), pressing and holding the manual Save button (500 ms, with a small haptic tick) opens the Auto-Save interval dropdown — on phones the compact toolbar hides the "Auto-Save:" button, so there was no touch way to reach it. A fired long-press suppresses the trailing tap so it never also triggers a manual save; text-selection/context-menu on long-press are suppressed. Desktop/PWA behavior is untouched (flag-gated by isAndroidApp()).

Mundus v0.8.1 — Work Log

NEW FEATURES / IMPROVEMENTS

A1. Settings toggle — Harper suggestions as a vertical list

  • New toggle in Settings → Editor & Scene Detection: "Show All Grammar Suggestions in a List" (stored in mundus_harper_vertical, applied live — no reload needed).
  • Off (default): unchanged horizontal popup with ‹ › cycling (←/→), Tab accepts.
  • On: all candidates (up to 3) render stacked vertically; the selected row is highlighted emerald with a check; ↑/↓ arrows move the selection (Tab still applies, click applies a row directly). Caret-arrow keys keep working normally in this mode since the popup no longer needs ←/→.

A2. DOCX import & export — full pass + verify loop

Discovery first: docx import (mammoth) and export already existed in the UI — but export was flat and lossy (no nested formatting, lists, colors, highlighters, images). What shipped:

  • Export rewritten (exportToDocx): recursive block walker — headings h1–h6, paragraphs, blockquotes, bullet lists (native docx bullets), numbered lists, nested inline formatting to any depth (bold/italic/underline/strikethrough), text colors (rgb/hex → docx hex), highlighter → real shading (w:shd fill), images (data-URL img → embedded media, type-detected png/jpg/gif/bmp, width-scaled), manual page breaks, zero-width-space scrubbing.
  • Verify loop (Playwright, headless Chromium, scripts/test_v081_docx_highlight.js) — 14 checks, all green:
    • docx export produces a valid file; title, H1, nested bold+italic, highlighter shading (w:shd), bullet list, numbered list, embedded image all verified by re-parsing the file with mammoth;
    • docx import roundtrip (own export → import): title + content + bold restored;
    • docx third-party import (a docx generated independently via the docx lib): H1, bold, paragraphs reconstructed;
    • PDF native roundtrip: export → re-import restores the document losslessly (title + content verified).
    • Bonus fix: hardened VisualImageOverlay's global click handler against non-Element targets (a latent target.closest crash on synthetic/edge events).

A3. Update-available indicator (green arrow)

  • New src/lib/updateCheck.ts: compares APP_VERSION against the latest published release of github.com/notbedd/mundus (GitHub releases API, unauthenticated). One check per app start, throttled by a 6-hour localStorage cache; offline/404/rate-limit = arrow simply stays hidden; a stale cache can never falsely claim an update (comparison is always recomputed against the running version).
  • New src/components/UpdateAvailableArrow.tsx: a green arrow chip next to the version badge in BOTH the Sidebar and the Atlas. Click opens the releases page (Electron routes it to the system browser via the existing window-open handler). Includes version-compare unit tests (semver-ish per-segment, v optional, malformed tags safe).

FIXES

F1. Storage space count — the 16 KB phantom

Root cause: Sidebar.calcBytes() added a hardcoded 1024 * 16 "base metadata overhead" to EVERY scope, and only documents+folders were measured.
Fix: new src/lib/storageEstimate.ts — REAL measurement, no constants:

  • per Mundus: JSON size (UTF-16, exactly how storage accounts bytes) of every record belonging to the project (documents, folders, characters, codex entries, timeline events, story links) + raw size of every per-project key (map markers, map image + name, world name, story-flow layout) + the project's slice of the config maps (codex sections, timeline/calendar) + per-document snapshots (resolved doc-id → owning project);
  • shared: every remaining mundus_* key (settings, dictionary, fonts…), with the global record lists and config maps excluded so nothing counts twice;
  • "All Mundus" = Σ(per-Mundus) + shared by construction — the rows now always reconcile; an empty Mundus shows its true (small) size; orphan keys of deleted worlds still count toward the total.
    Covered by 11 new unit tests (test_v081_helpers.ts), incl. the exact reconciliation scenario.

F2. Highlighter re-latch after selection-apply — FIXED (and verified in a real browser)

The v0.8.0 approach cleared the browser's typing style but couldn't stop DOM-adjacency inheritance: with the caret resting at the end of the freshly painted <span>, Chromium keeps inserting typed text INTO the styled element. Empirically proven with a DOM-mutation spy: even with the caret placed in a plain text node parked at the span boundary, Chromium canonicalizes the insertion point back to the span's end (every keystroke bounced into the highlight; a typed space became &nbsp; inside it).
The fix (caret anchor, take two):

  • After a one-shot apply, the editor inserts a non-editable zero-width anchor (<span data-mundus-caret-anchor contenteditable="false">​</span>) right after the outermost styled element and places the caret immediately after it. The non-editable element breaks the position equivalence — typed characters now land in the plain text after the highlight (space appears as &nbsp;, exactly like normal typing).
  • Three anchor shapes were tested empirically; the contenteditable=false variant is the only one Chromium respects.
  • The v0.8.0 collapsed hiliteColor transparent stamp was REMOVED — it materialized a style holder that dragged the insertion point back to the boundary, undoing the anchor.
  • Lifecycle: anchors are swept by the input + selection-change pipelines (kept only while the caret still rests in their protection zone, removed after), and cleanHtmlForSaving strips them (and their ZWSP) so saved/synced/exported content never carries them.
  • E2E proof (same 14-check Playwright run): select → apply Pale Gold → type and beyond words → the span stays Maren knew the tide and the typed text sits outside with no background; anchor cleaned up after the caret moves on.

F3. "In This Scene" drawer unreadable in light mode

The drawer hard-coded dark-theme text on theme-aware cards (near-white titles on white cards = invisible; dark bg-neutral-900 inputs and panels in light mode). Fixed across the whole drawer: entry/character titles, roles, overview, subtitles, expanded dossier (title, tags, overview card, chronicle, footer), the inline edit form (inputs, textareas, labels, buttons) — all now theme-aware via isDarkMode/dark: variants. Dark mode is pixel-identical; light mode is fully readable.

F4. Top-bar "In This Scene" button counted inner-word matches

The button's badge had its OWN substring detection (plainText.includes(...)) — "atulliusx" lit the button for "tullius" while the (fixed) drawer showed nothing. The badge now runs the exact same shared whole-word helpers the drawer uses (detectInSceneEntryIds / detectInSceneCharacterIds), same debounce. Button and drawer can no longer disagree.


FOR ANDROID ONLY

AN1. Long-press Save → Auto-Save dropdown (opens nothing → fixed)

Root cause: the document-level mousedown outside-close handler. The long-press opens the menu while the finger is still down; when the finger lifts, the browser synthesizes a mousedown on the Save button — which sits OUTSIDE the menu ref — closing the menu in the same instant it opened (and the trailing click was suppressed, so nothing visible ever happened).
Fix: while the long-press flag is still hot, the Save button counts as "inside" for that one closer (flag is consumed right after by the click-suppression, so every other outside tap still closes the menu normally). Desktop behavior untouched (flag only exists on the Android path).

AN2. Content no longer renders behind the status bar

The Android WebView draws edge-to-edge but never populates CSS env(safe-area-inset-top) — the iPad-update padding evaluated to 0 on Android.
Fix: MainActivity now measures the REAL top inset (status bar + display cutout, incl. Android 11+ WindowInsets.Type) via an OnApplyWindowInsetsListener on the WebView and publishes it to the page as --mundus-safe-top (with a resource-based early fallback + two delayed re-publishes to win the page-commit race). #app-root pads the top with max(env(safe-area-inset-top), var(--mundus-safe-top)) — bottom stays fully edge-to-edge (unchanged), iPad/desktop behavior identical.

Mundus v0.8.2 — Worklog

FIX 1 — Storage popup:

The per-Mundus row only counts that Mundus's own records + per-project keys. The rest of
the app's quota lives in shared, app-wide localStorage keys that belong to no single
Mundus.

Shared key What it is
mundus_local_auto_backups Local auto-backup snapshots (default ON, keeps the last 5) — each one is a full backup of all your Mundus + settings
mundus_projects The worlds list itself
mundus_settings-family Theme, accent color, standard palette, keybindings, default fonts, autosave choice, custom Firebase config…
mundus_custom_dictionary / fonts / ImgBB key App-wide extras
mundus_pending_sync_* Offline sync queues

What changed: the tooltip (sync icon hover, both expanded + collapsed sidebar
variants) now shows a third row — "Shared app data" — plus a one-line footnote, so
the math adds up visibly: This Mundus + other Mundus + Shared = All Mundus.

FIX 2 — Android: hold-save finally opens the Auto-Save dropdown

v0.8.0/v0.8.1 wired the long-press to the desktop save button, but phones show a
different icon-only save button in the mobile top bar — and even when the state
flipped, the dropdown was rendered inside the desktop toolbar row which is
hidden md:flex (invisible on phones). Two bugs, zero visible result.

  • Long-press handlers now live on the mobile save button too (500 ms, haptic tick,
    trailing click still suppressed so it doesn't also save).
  • The Auto-Save popover now renders anchored under the mobile save button (fully
    on-screen, verified in a 390 px viewport); picking an interval applies + closes.
  • Desktop behavior unchanged.

FIX 3 — Android: status bar (the big one)

Root cause of the "huge blank chunk": v0.8.1's MainActivity published the top inset in
raw device pixels, but WebView CSS px are density-independent. On your phone
(~3.5× density) the ~132-px status bar became a 132-CSS-px padding ≈ 462 device px —
~3.5× too tall. Measured on your screenshot: ~470–500 px. Exactly that bug.

  • MainActivity now divides the measured inset by the display density → exactly one
    status bar of padding. It also remembers the measured value and re-publishes that
    (instead of the coarse resource fallback) after page load, avoiding double-padding.
  • Mundus Atlas renders outside #app-root, so it never got any padding (your
    screenshot: logo behind the clock). The Atlas shell + its sticky header now pad by
    the same inset.
  • Sidebar on phones is a fixed inset-y-0 drawer — also unreachable by #app-root
    padding (your screenshot: "A4 TEST" behind the time). Padded on the mobile breakpoint.
  • Mundus View backstage overlay + its floating nav buttons: same treatment.
  • All mode UIs now shift down by exactly one status bar — no more compressed layout.
  • iPad/desktop/Electron untouched: iPad PWA keeps its env(safe-area-inset-top)
    path (vars it never receives evaluate to 0), desktop stays at 0 padding (E2E-verified).

Files changed

  • android/app/src/main/java/com/mundus/app/MainActivity.java — density-corrected inset publishing
  • src/index.css — safe-top for #app-root (now also reads Capacitor's --safe-area-inset-top), .mundus-atlas-shell (+ sticky header), #app-sidebar (mobile), #start-menu-overlay (+ floating nav)
  • src/components/MundusAtlas.tsx — mundus-atlas-shell class
  • src/components/TextEditor.tsx — mobile save button long-press + shared Auto-Save popover (renderAutoSaveMenuPopover), second button ref in the outside-close guard
  • src/components/Sidebar.tsx — "Shared app data" row ×4 tooltip variants + footnote
  • Version files: package.json, package-lock.json (×2), src/constants.ts, index.html (title + og), electron/main.ts, android/app/build.gradle, public/sw.js

Full Changelog: v0.7.98...v0.8.2