feat(file-viewer): browse a versioned file's git history
Nightly Build / build (push) Successful in 8m5s

A clock button in the file viewer header lists the versions of a file
whose project keeps a git history; picking one shows the file as of
that commit, read-only, with a banner back to the current version.

A past version is never served from the working tree: the whole
repository is materialized at that revision (git archive streamed
through tar into a size-bounded, immutable-by-rev cache) and every
fetch — content, compiled LaTeX, markdown images, downloads — resolves
inside that tree, so dependencies are contemporaneous with the file:
a .tex compiles against its \input's and images of that moment.

Backend: new git_versions module (repo discovery bounded by the
workspace mount, host-git log/rev-parse/archive, extraction cache with
oldest-first prune) + GET /api/file/versions and a rev param on
GET /api/file (rev is the ETag; never X-Writable). Frontend: history
mode in FileViewerBase shared by the desktop and mobile viewers —
popover, banner, watcher paused while browsing, rev propagated to
every /api/file URL it builds.
This commit is contained in:
Daniele
2026-08-10 14:27:48 +01:00
parent cd641ab89e
commit 1515492938
14 changed files with 950 additions and 15 deletions
+96
View File
@@ -260,6 +260,102 @@
flex-shrink: 0;
}
/* ── History mode (git versions) ───────────────────────────────────────────── */
.fv-history {
position: relative;
display: inline-flex;
}
/* Transparent click-catcher that closes the popover on outside click. */
.fv-history-overlay {
position: fixed;
inset: 0;
z-index: 1040;
}
.fv-history-pop {
position: absolute;
top: calc(100% + 6px);
right: 0;
z-index: 1050;
display: flex;
flex-direction: column;
gap: 2px;
min-width: 260px;
max-width: min(340px, 80vw);
max-height: 320px;
overflow-y: auto;
padding: 4px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: var(--radius-md);
box-shadow: var(--card-shadow);
}
.fv-history-row {
display: flex;
flex-direction: column;
gap: 1px;
padding: 0.45rem 0.6rem;
text-align: left;
background: none;
border: 0;
border-radius: var(--radius-sm);
color: var(--bs-body-color);
cursor: pointer;
}
.fv-history-row:hover { background: var(--sidebar-hover); }
.fv-history-row.active { background: var(--sidebar-active-bg); }
.fv-history-date {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.72rem;
color: var(--bs-secondary-color);
}
.fv-history-current {
padding: 0 0.35rem;
font-size: 0.66rem;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
}
.fv-history-subject {
font-size: 0.8rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Banner shown between header and body while viewing a past revision. */
.fv-version-banner {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem;
padding: 0.45rem 0.85rem;
background: var(--bs-info-bg-subtle, #cff4fc);
border-bottom: 1px solid var(--bs-info-border-subtle, #9eeaf9);
color: var(--bs-emphasis-color, inherit);
flex-shrink: 0;
}
.fv-version-banner > .bi {
color: var(--bs-info-text-emphasis, #055160);
flex-shrink: 0;
}
.fv-version-text {
flex: 1 1 200px;
min-width: 0;
font-size: 0.82rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ── PDF preview ─────────────────────────────────────────────────────────────── */
/* PDFs are drawn by <pdf-view> (web/components/shared/pdf-view.js) on canvas, not