Purpose: Non-Linear Temporal Anchors #
While chapters possess a default baseline in-universe date (e.g. world_time = "1422-04-12"), stories frequently feature flashbacks, memories, simultaneous cuts, and interludes within a single scene.
FleshNote allows authors to highlight any passage and assign a Time Override using the 8-color chronological palette. The assigned time override appears as a vertical accent bar on the left editor gutter.
Multi-Paragraph Bounding Box Geometry #
In ProseMirror/TipTap, inline marks cannot cross block boundaries (<p> tags) as a single contiguous HTML element. When an author selects multiple paragraphs and assigns a time override, ProseMirror automatically splits the mark into separate <span data-time-id="ID"> tags inside each paragraph block.
The Bounding Box Union Algorithm (`TimeGutter.jsx`)
To render a single continuous vertical bar spanning all highlighted paragraphs, src/renderer/src/components/TimeGutter.jsx calculates the union of all matching DOM rects:
// 1. Query all time-link spans within the editor column
const allSpans = Array.from(container.querySelectorAll('[data-time-id]'));
// 2. Group spans by timeId
const groups = {};
allSpans.forEach((el) => {
const id = el.getAttribute('data-time-id');
if (!groups[id]) groups[id] = [];
groups[id].push(el);
});
// 3. Compute top-to-bottom bounding box (union of all spans in the group)
const positions = {};
Object.entries(groups).forEach(([id, els]) => {
let top = Infinity;
let bottom = -Infinity;
els.forEach((el) => {
const rect = el.getBoundingClientRect();
top = Math.min(top, rect.top - containerRect.top);
bottom = Math.max(bottom, rect.bottom - containerRect.top);
});
if (top < Infinity) {
positions[id] = { top, bottom };
}
});
Because marker positions are dynamically measured from the live TipTap DOM, the gutter bars automatically reposition and resize as the author types or deletes text, with zero manual handle adjustment required.
Flashback Reactivity & Knowledge Shift #
The Time Gutter is not merely a visual decoration; it actively controls the IDE's Effective World Time Context:
data-time-id mark. If so, the active in-universe date is instantly shifted to that override's date.
This dynamic shift triggers immediate reactivity across inspected panels:
- Character Epistemic Knowledge: Hides any facts learned after the flashback date.
- Character In-World Age: Live-computes the character's exact age during the flashback.
- Location Weather: Re-evaluates 5-level recursive weather inheritance for that historical date.
Deletion Cascade & Mark Scrubbing #
When an author deletes a time override via the gutter context menu:
- TipTap DOM Traversal (`TimeLinkMark.js`): The custom command
removeTimeLinkById(timeId)executesstate.doc.descendants, stripping everytimeLinkmark with that ID across all paragraphs in a single transaction. - SQLite Database Cleanup: The IPC call
DELETE /api/project/world_times/{id}drops the record from theworld_timestable. - Gutter Remeasure: The gutter re-measures DOM rects on the next animation frame, cleanly animating the bar away.