The 2-minute timer + tick ref decayed verified to stale-known and rendered
a clock icon, but the user can always click the badge to re-check. Removing
the timer simplifies the hook and the badge has one fewer visual state.
Removes the temporary on-screen scroll diagnosis panel and the global
window.__scrollAtClick stash. The anchor scrollTop is now captured
synchronously at addStackedNote entry and threaded through
scrollToFocusedNote and scrollToNoteElement to scrollToElement, so no
state survives across calls — nothing to reset on repo or page change.
Capture mainApp.scrollTop synchronously when addStackedNote runs and
snap the scroll back to that value before scrollIntoView fires, so
the smooth scroll begins from where the user actually tapped rather
than from a position drifted by momentum or async work.
Both html/body and #main-app being scrollable on mobile made
scrollIntoView animate two ancestors at once, shifting the start
frame of the smooth scroll. With body locked, #main-app is the only
scroller and the animation matches the user's actual position.
Dynamic viewport units rescale every note when the mobile address bar
grows or shrinks, shifting the scroll target by the address-bar height
mid-flight. Small viewport units stay constant across address-bar
transitions so the smooth scroll lands where it was aimed.
Native scrollIntoView reads the element position at scroll time and
picks the right scrollable ancestor itself, sidestepping iOS Safari
quirks with scrollTo on overflow containers and visual-viewport shifts.
Replace the (index + 1) * clientHeight math and 80ms setTimeout with a
scrollToElement helper that reads getBoundingClientRect inside rAF, so
the smooth scroll starts from the user's actual position even when the
note is freshly mounted.
The root fade overlapped smooth scrolls triggered when stackedNotes
mutated, making the scroll appear to start from the snapshot's frame
instead of the user's actual position.
Clicking the badge while it shows outdated now pulls the latest version
from GitHub when there are no unsaved edits, or opens the conflict
modal when edits are in flight. Previously the click only re-ran the
same freshness check, so the badge appeared dead.
Adds a Tabler-icon badge in the stacked-note action bar showing whether
the loaded copy still matches GitHub HEAD (verified / outdated / offline
/ checking / unknown / stale-known). The save flow now re-checks before
the PUT and opens a conflict modal when GitHub has moved on, with three
explicit choices: discard local edits and pull, overwrite anyway, or
cancel. Race-condition 409s from the PUT itself are routed through the
same modal.
updateFile/createFile now return { sha, conflict } so 409/422 from GitHub
can drive a UI flow instead of being swallowed as a generic save error.
Also adds fetchLatestSha(path) for cheap freshness checks against HEAD.
The .husky/_pre-push script was renamed from pre-push, which
disables it under husky v9. With no remaining active hooks, husky
is dead weight, so remove the dependency and prepare script too.
Grid items default to min-width: auto, so the 5×220px scroll strip
forced the 1.2fr column to its intrinsic width and pushed the right
column out. min-width: 0 lets the track shrink and overflow-x scroll.
Replaces the static "From the open network" CTA and sidebar button with a
horizontal strip and compact list of recent public notes fetched from the
public api.remanso.space/notes endpoint, so visitors can taste the network
before clicking through. Includes shimmer skeletons and a quiet fallback
when the endpoint is unreachable.
The boolean guard flipped synchronously before the async plugin load
resolved, so concurrent callers (e.g. multiple stacked non-markdown
notes mounting on reload) returned early and rendered before
markdown-it-shikiji was attached to the shared md instance. Cache the
in-flight promise instead so all callers await the same resolution.
Re-pin .note and .stacked-note to 100dvh on mobile and bring back the
container height in useResizeContainer so (index + 1) * height has a
reachable scroll target. Switch the polled scroll helper to that same
formula instead of offsetTop.
Move position: sticky from the global .note rule into the desktop
@media block of the scoped stacked-note components, so mobile no longer
inherits sticky positioning (and no top is set there).
A single nextTick is not enough for a freshly added stacked note to be
in the DOM, so the mobile scroll target was computed against a null
element. Poll with requestAnimationFrame (mirroring scrollToHashInNote)
and use offsetTop, with an (index + 1) * height fallback.
Non-markdown files opened as stacked notes are now highlighted using
the existing markdown-it-shikiji pipeline (4-backtick fence wrapping)
with a h1 filename heading. Edit controls are hidden for code files.
Adds alloy language grammar and a fileLanguage utility mapping
extensions to Shikiji language IDs.
Vue reactive Proxies cannot be serialized by the Structured Clone
Algorithm used by postMessage/Comlink. Use toRaw() on this.files and
this.userSettings before passing them to data.update() to avoid the
DataCloneError.
The skeleton was conditioned on `isLoading || !hasContent`, so it
persisted forever when readme resolved to null (e.g. private repo
visited while logged out). Skeleton now only shows while loading.
A click on a child of an <a> (e.g. nested <strong>, <em>, <code>, icon)
made event.target a non-anchor, so getAttribute('href') returned null
and the handler bailed without preventDefault. The browser then
performed the native navigation, which for relative links like
'../note.md' resolved against the current /:user/:repo URL and the SPA
re-routed treating the destination as a new repo.
The page width from .remanso.json was only applied after an async
PouchDB + network fetch, so notes briefly rendered at the default
500px before snapping to the configured value. Persist pageWidth
alongside the existing font cache (key renamed to remanso:layout:*),
so it is read synchronously during setUserRepo and applied before
the first render. Also always reset --note-width with a default
fallback to prevent stale values leaking across repo navigation.
Pure-fragment links (#heading) used to fall through to the browser's
default jump. Handle them in the click listener and scope the lookup
to the same stacked note so identical heading ids in other notes
don't win, with smooth scroll behavior to match cross-note anchors
into already-stacked notes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>