Skip to content

F um - #1

Merged
sumbad merged 41 commits into
masterfrom
f-um
Sep 28, 2026
Merged

sumbad merged 41 commits into
masterfrom
f-um

Conversation

@sumbad

@sumbad sumbad commented Sep 26, 2026

Copy link
Copy Markdown
Member

No description provided.

- .um container: zip (fflate) + manifest, deterministic writes, unknown
  fields/entries preserved across saves, mini-ULID ids, atomic writes,
  fingerprint-based external change detection with conflict modal
- notebook view: flat document of collapsed sections (blog page/folding
  model), lazy per-note prosemirror editors, shared toolbar, add note,
  inline rename, autosave
- images packed into the container as assets (assets/<id>.<ext>),
  referenced by stable asset id, object-url display, gc on save;
  other attachments stay external vault links
- create notebooks from the New inscriptum modal (type selector) and
  the New notebook (.um) command
- section titles mirror the note document's first line (manifest title
  is a synced mirror); visually-hidden is now a global utility
- per-section ghost controls: add-after and delete (delete with undo
  notice; native Notice.addAction when available, fallback button)
- blog-style section layout: page order + fold chevron in the left
  margin, vertical rule on expanded sections, light separators,
  wider content column (~768px text width)
- saves are serialized so rapid structural ops (delete then undo)
  land on disk in trigger order; editor flush no longer resurrects
  a just-removed note
- chevron/order: bare hairline glyphs without button chrome, muted
  color darkening on hover (0.2s transition)
- add/delete controls: always visible at 30% opacity, the group's
  hover raises it to full strength, a hovered icon darkens —
  the blog's three-level emphasis, mapped to obsidian theme vars
- separators moved to page bottom border (all pages but the last);
  collapsed row restyled to the blog's muted title line
- chevron: 44x44 hairline (stroke 0.5) glyph, expanded points down;
  add/delete icons: 24px, stroke 1 — verbatim from the blog
- fully bare buttons: neutralize obsidian's
  button:not(.clickable-icon) base (background/box-shadow) for base,
  hover and focus states
- expanded margin top realigned with the first text line
- add-after uses a plain plus icon; delete uses a red trash icon
  (var(--text-error) in all states)
- deleting asks for confirmation first: 'Страница будет удалена без
  возможности восстановления, продолжить?' with Удалить/Отменить
  (new reusable ConfirmModal); the undo notice is gone — the dialog
  wording promises no recovery
- dialog wording softened to 'Страница будет удалена, продолжить?'
  since recovery is now possible
- after confirming, a notice offers Отменить for 7s; the save chain
  keeps the delete and undo writes in order
- english strings for the delete dialog, notice and undo action
  (the app is english-only so far)
- undo action inside the notice restyled as a compact underlined
  text action that fits the notice in both themes
- the red trash follows the same three-level emphasis as the plus:
  faint red at rest, stronger on the controls-group hover, full red
  on its own hover (color-mix steps of var(--text-error))
- the first page offers no delete control (anchors the notebook,
  same as the blog's order-0 page)
color-mix steps (45/75/100%) read as the same red at icon size, and
the own-hover rule was outranked by the group-hover one. Drive the
gradation with the icon's opacity instead — 0.5 idle (x group 0.3),
0.75 on the group hover, 1.0 on its own hover — with the own-hover
selector outranking the group rule.
- the toolbar binds as soon as a page is expanded (most recently
  expanded wins until focus changes), not only after a click
- collapsing the page that owns the toolbar rebinds it to another
  expanded page; with all pages collapsed the bar stays visible as an
  empty idle strip (same 40px row, no layout jump)
- media bubble menu never appeared: MediaBubbleMenuElement requires
  the app prop (its generator only starts once required props are
  set) — pass it alongside editor
- toolbar/picker inserts saved files next to the document: a
  brand-new image node (no data.id) fell through the asset resolver
  to the vault-based handler; route the picker's onFileSelected
  through container packing instead
- optional per-note 'expanded' flag in the note descriptor (absent =
  collapsed, the spec's initial default); spec sections 8 and 8.5
- UmNotebook.isExpanded/setExpanded; expansion changes mark the
  container dirty and go through the debounced autosave
- NotebookView folds/expands from the manifest instead of in-memory
  state, so the layout survives reopen, reload and file moves
- a restored (undo) note brings its expanded flag back with it
(starter note persisted expanded in the manifest; format-only
updates to test files)
17px icons with a 1.25 stroke pinned to the right edge of the
section column
- controls row moves inside the page frame, above its bottom
  separator — the line unambiguously groups the add/delete controls
  with their page (previously they sat under the next page's line)
- every page now has the bottom separator, including the last
- bigger icons (20px) and a soft red tint behind the trash on its
  own hover, making the gradation unmistakable
- 32px of breathing room between the previous page's separator and
  the next page's content
- dropped the red tint background from the trash's own hover (it
  read as button chrome); the emphasis is icon-only now: full
  opacity plus a slightly thicker stroke
the previous padding-top sat inside the next page's frame, so the
frames still touched; the gap is now a margin between sections
plain .note files keep their 32/48/120 padding; notebook pages frame
themselves (32px inter-page margins + controls row), so the editor's
own top/bottom padding only added dead space. Expanded-section
chevron realigned to the first line.
the expanded-state margin override shifted the chevron vertically on
every toggle; one fixed top now serves both states, and the rotation
between right (folded) and down (expanded) is animated (0.2s)
- Mod+F opens a notebook-wide search panel: case-insensitive search
  across ALL pages (collapsed included) via a plain-text index with
  PM position mapping (cross-validated against the live document's
  own search); jump to match lazily expands the page, selects and
  scrolls; expanded pages get highlight decorations; Enter/Shift-
  Enter and chevrons navigate 1/n
- hotkey routing parity with NoteView: physical-shortcut plugin per
  page editor (non-Latin layouts), colliding Obsidian commands patch
  through NotebookView focused editor, Mod+K link layer forwarding
- element tags get a per-build identifier in production builds too:
  a plugin toggle-off/on no longer leaves stale classes in the
  window registry (Illegal constructor for images/attachments)
- jumping to a match no longer selects text or moves focus: it
  scrolls the page to the match and marks it with the active search
  decoration (browser-search behavior)
- pages mounted by a search jump no longer autofocus, so the search
  input never loses focus while typing or navigating
- duplicate a page right after the original (deep copy, shared image
  assets, '(copy)' suffix written into the copy's first line so the
  title mirror keeps it)
- blog-style pages navigation drawer on the left (☰ toggle, hidden by
  default): numbered page list with titles, click expands/jumps to the
  page, active page follows the focused editor
- drag & drop reorder by the section gutter: pointer drag with a
  6px threshold, drop indicator line, manifest order persisted;
  click-vs-drag disambiguation on the gutter
- toggle: bare 38px menu-2 glyph (hairline stroke 1) hanging outside
  the closed drawer, docking inside when open; neutralizes obsidian
  button chrome for base/hover/focus
- nav list: blog TOC look — inset line on the panel, uppercase title
  row, page rows with the number hanging left over the panel edge,
  muted headers truncating with ellipsis
…scroll

- ☰ morphs into ✕ (crossfade + rotation) when the drawer opens; the
  toggle docks flush to the drawer's right edge (blog behavior)
- toc rows: compact 1.6rem left indent, number column right-aligned
  right before the header — one aligned line
- the inner page editor never scrolls by itself (overflow visible):
  the notebook scroller owns scrolling, phantom nested scrollbars gone
the previous flat right: 0 put the toggle off-screen once the drawer
slid out (-100%); restore the blog's two-position scheme — right
-48px (outside, visible at the view's left edge) when closed, right 0
(docked inside) when open
code identifiers, file names, CSS classes, view type, command id,
UI strings and the manifest type value ('type': 'notepad') — no
backward compatibility is required yet. Spec wording updated to
match (Notepad container).
…e schemas (schema + schemaVersion)

- Title page (order 0): blog-draft format (large title + summary, placeholders,
  focused field labels), no delete/duplicate/collapse/reorder, no left frame
  rule, add-after only.
- .um manifest: per-page schema family + version (title/plain, v1) with legacy
  inference and write-back; regular pages created content-only, display title
  derived from the first line (heading/text block, else Untitled).
- Editor profiles (editorSchemas.ts): named extension compositions per family
  sharing one plugin set; TitleDoc/PlainDoc keep the noteDoc top-node name and
  an optional noteTitle slot so existing containers parse without migrations.
- Newer-version pages render a notice instead of an editor and are preserved
  verbatim on every rewrite.
- Snapshot tests pin each profile's canonical schema shape (version-bump guard);
  16 model tests for inference/migrations/derivation; 367/367 green.
…or both themes

- noteSummary joins noteTitle in DRAG_HANDLE_EXCLUDED_TYPES: the floating
  block handle never appears next to the summary (the cover header is
  fixed, spec 9.1); pinned by a title-profile drag-handle test.
- Summary styles follow the blog draft via theme variables: 15px/18px,
  weight 400, 12px vertical rhythm, --text-muted color (the light-scheme
  face of the blog's #79828b) — correct in light and dark schemes.
@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 0/5

[High risk] Adds a new file format and storage layer with dependency.

The PR is not safe to merge until the compilation error and the notepad data-loss and lifecycle failures are fixed.

Findings

  1. P1 Missing type import ▶
  2. P1 In-flight edits lose save flags ▶
  3. P1 Conflict writes can land out of order ▶
  4. P1 Removed pages leave editors alive ▶
  5. P1 Asset paths overwrite archive entries ▶
  6. P1 Newer pages can lose assets ▶
  7. P2 Double-click rename does nothing ▶
  8. P2 Security Archive decompression lacks bounds ▶

Summary

Adds .um notepad containers, per-page editor profiles, page management and search, packed images, and Obsidian creation commands. The new implementation needs fixes to compilation, save ordering, editor teardown, and container preservation before merging.

Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[".um file"] --> B["readUmFile"]
  B --> C["parseUmContainer"]
  C --> D["UmNotepad"]
  D --> E["NotepadView page editors"]
  E --> F["flushSave / saveChain"]
  F --> G["serialize + asset GC"]
  G --> H["atomic vault write"]
Loading

Reviews (1) · Last reviewed commit: "fix: Image layouts in `.um` notepads are..."

Comment thread src/storage/um/umNotepad.ts
Comment thread src/NotepadView.ts Outdated
Comment thread src/NotepadView.ts Outdated
Comment thread src/NotepadView.ts
Comment thread src/storage/um/umContainer.ts
Comment thread src/storage/um/umNotepad.ts
Comment thread src/NotepadView.ts Outdated
Comment thread src/storage/um/umContainer.ts
Review follow-ups for the .um notepad view:

- Save: clear the dirty flags before the write, not after — an edit
  landing during the disk wait (e.g. flushed by a collapse) keeps its
  flag for the next save, and a failed write restores them. Conflict
  resolution ("Keep local") now joins the shared save chain so an
  in-flight autosave cannot land after it and overwrite the choice.
- Editors: page editors are handed to NoteElement's props.editor so the
  host element's cleanup destroys them (collapse, page removal, close);
  an in-flight expansion aborts when the section collapsed while
  waiting for the container; destroyAllSections drops the focused
  editor reference.
- Drop the dead dblclick handler on collapsed rows: the first click
  expands and removes the row before a dblclick can ever land; rename
  stays on the edit glyph.
Review follow-ups for the .um storage layer:

- Size caps against zip bombs: vault reads reject files over the
  compressed-size cap before readBinary, and parsing bounds the total
  inflated size — enforced per entry from the ZIP central directory's
  declared sizes before it inflates (unzipSync preallocates each entry
  at its declared size and never grows it). Caps are overridable for
  tests; a hostile archive fails cleanly with UmError too-large.
- Write guard: asset descriptors and unknown entries can no longer
  overwrite the manifest or a note document (a hostile registry naming
  a protected path is skipped at write time).
- Asset GC pauses while any page is uninterpretable (newer version,
  unknown family, failed migration): the page is preserved verbatim, so
  its media survives even if its references use unknown shapes.
- Add the missing UmContainerData import.
- In-document folds (headings/tasks) on .um pages persist device-local
  per page via the shared foldPersistence module (NoteView refactored
  onto it); stale positions are bounds-checked before nodeAt
- Page column fixed at 918px on desktop — text column 748px, parity
  with .note; narrow windows scroll horizontally, the nav flip guards
  the horizontal scrollbar, mobile keeps the compressing fit
- The 'Add note' row after the last page is always shown (was: empty
  notepad only); hover-only background, margin-top 32px
- Drop the ✎ rename glyph, the inline title input and the orphaned
  UmNotepad.setDisplayTitle API (+3 tests): renaming happens by editing
  the page's first line — the title source of truth (um-title-sync)
- collapseSection rebuilds the nav list, so a rename made in the editor
  reaches the nav right away
- Save-time syncNavTitles refreshes the nav while a page stays expanded
  (the drawer is open by default on wide screens); a title-signature
  compare exits early so no-op saves never rebuild the list
The build is vite-only, so type errors never surfaced — strict mode is on
and there were zero suppressions, but 58 errors had accumulated silently
(29 in production code, 29 in tests).

- wire the obsidian mock to the real API types (type-only imports, erased
  at runtime) so mock TFiles pass into production functions
- type DataAdapter.getFullPath via a feature-detected intersection type;
  handle stat() returning Stat | null (noteStorage, umVault)
- NotepadView: InstanceType aliases for the custom elements, typed
  bubble-menu factories, null guards (scroller, drop-line, drag)
- texto: null-safety in table/code-block helpers; widen Mark/Extension
  dynamic-config casts (tiptap pattern, no behavior change)
- include the root tests/ dir in tsconfig (5 surfaced errors fixed),
  allowJs for the release-lib import
- new CI workflow: typecheck + lint + test on every push/PR; document
  typecheck in README

Runtime changes are limited to guards on paths that previously would have
crashed. Verified: typecheck/lint clean, 378 tests pass, build works.
@sumbad
sumbad merged commit 2b70bc2 into master Sep 28, 2026
2 checks passed
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