phase: 181_svg_document_preview
**Phase 181 — final verification pass (all tasks already complete; verified + docs pass)**
- Verified: `app/rag/svg_text.py` (100 % cov), vector branch in `_index_image_file`, `_row_kind_mismatch` text→image migration, `IMAGE_MIMES[".svg"]`, sandboxed CSP on `GET /api/documents/{id}/image`, `.doc-image-img--vector` in `document.js`/`styles.css`, figure/thumbnail deliberate non-changes.
- Tests/lint (exact commands): `uv run pytest --cov=app --cov-report=term-missing` → **4265 passed, TOTAL 99 %** (exit 0) · `uv run pytest tests/e2e/test_svg_document_preview.py -v --no-cov` → **5 passed in isolation** · `uv run ruff check .` → clean · `uv run pyright` → **0 errors**.
- Regression (each run in isolation, `--no-cov`, all rc=0): `test_image_documents`, `test_chat_image_questions`, `test_answer_images`, `test_chat_answer_clusters`, `test_image_anchor_context`, `test_image_upload_feedback`, `test_markdown_inline_links_images`, `test_prose_word_wrap`, `test_pdf_support`, `test_pictorial_pdf`, `test_garbage_pdf`, `test_pdf_image_extraction`, `test_pdf_image_context`, `test_pdf_pages_in_sources`, `test_pdf_strip_additive_selfheal`, `test_sync_image_progress`.
- Completion criteria: ①SVG→`is_image`+`image_path`, no vision call, own `<title>/<desc>/<text>` — **met**; ②`image/svg+xml` + `default-src 'none'; sandbox` + `private, max-age=3600`, raster headers byte-unchanged — **met**; ③chat figure → modal `<img>`, no XML — **met**; ④`/document.html` full-width vector in `.doc-image` + text below — **met**; ⑤`` paints — **met**; ⑥raster/PDF unchanged — **met**; ⑦all gates green — **met**; ⑧commit + phase-dir move — harness-owned.
- Documentation: found and fixed README inconsistencies — stale forward reference "left to phase 181 / is phase 181's job" → completed-tense sizing note; "Updating the documents" standalone-image list missing `svg`; new **Raster only** bullet for question uploads (`.svg` → 422) documenting the `chat_images` guard; missing `svg_indexed=N` / `import: svg indexed` log contract; env-table heading `122/123/124` → `122/123/124/181`; new troubleshooting entry for an `.svg` showing raw XML. Full audit: every `Settings` field and every `.env.example` var is documented; pages/commands/scripts all exist. AGENTS.md verified accurate (phase-181 §5 clause present) — no changes needed beyond what the phase wrote.
- Notable deviation (kept from task 02): `app/api/chat_images.py` subtracts `_VECTOR_IMAGE_EXTENSIONS` from the question-image accept set — a required consequence of LOCKED A7 (`svg` in the default image set) since `_CHAT_IMAGE_FILENAME_RE` refuses to *serve* `.svg`; the regex itself is untouched, as the phase specified.
- Note for the commit: changes include `README.md`, `AGENTS.md` and `.env.example` — the phase file's `git add app/ frontend/ tests/ .env.example .agents/phases/` line omits the two markdown files; stage them too.
- Next pending phase: `182_verbatim_show_text`.
This commit is contained in:
+11
-1
@@ -2498,7 +2498,17 @@ function appendRelated(wrap, related) {
|
||||
* replaced, in place, with a plain `.source-chip` anchor for the same
|
||||
* document — the citation survives a lost byte copy, never a
|
||||
* broken-image icon (the row's bytes route 404s when the copy was
|
||||
* lost). */
|
||||
* lost).
|
||||
*
|
||||
* Phase 181 (task 04, design D7): DELIBERATE NON-CHANGE for SVG. The
|
||||
* figure keys off image_url alone, and a size-less (viewBox-only)
|
||||
* vector drawing scales INSIDE the fixed cap of .source-image-img
|
||||
* (max-height: 96px + max-width: 100% + object-fit: contain), so the
|
||||
* compact chat citation needs no format-aware sizing; the full-size
|
||||
* preview is the modal this figure/chip opens (doc.format is known
|
||||
* there — see docImageBlock in document.js). NO format field is
|
||||
* added to SourceRef — the wire contract stays byte-identical for
|
||||
* every text doc (the phase-83/122 contract). */
|
||||
function appendSourceImageFigure(meta, s) {
|
||||
const label = `${s.source}/${s.path}`;
|
||||
const fig = document.createElement("a");
|
||||
|
||||
@@ -573,7 +573,26 @@ function docImageBlock(doc) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "doc-image";
|
||||
const img = document.createElement("img");
|
||||
img.className = "doc-image-img";
|
||||
// Phase 181 (task 04, design D6/D7): SVG is a VECTOR image doc. A
|
||||
// drawing carrying only a viewBox has NO intrinsic size, so the
|
||||
// default replaced-element box (300×150) would sit small inside the
|
||||
// full-width .doc-image card; the --vector modifier (the base class
|
||||
// is KEPT so every current rule still applies) lets width: 100%
|
||||
// span the card — the full-size preview. doc.format is the ONLY
|
||||
// place in the app that knows the type: the bytes URL is
|
||||
// extension-less and SourceRef carries no format field by design
|
||||
// (D7). LOCKED A3: the SVG is NEVER inlined into the DOM —
|
||||
// img.src = doc.image_url stays createElement + property
|
||||
// assignment (this file's XSS contract, unchanged). Legacy
|
||||
// honesty (D6): a row still indexed as TEXT (an .svg under
|
||||
// BOR_IMAGES=0, or not yet re-synced) keeps rendering its XML in
|
||||
// .doc-raw — the truth about that row (there are no bytes behind
|
||||
// it); task 02's kind-mismatch guard converts it on the next sync.
|
||||
// Nobody "fixes" that by sniffing the content string.
|
||||
img.className =
|
||||
doc.format === "svg"
|
||||
? "doc-image-img doc-image-img--vector"
|
||||
: "doc-image-img";
|
||||
img.src = doc.image_url;
|
||||
img.alt =
|
||||
typeof doc.summary === "string" && doc.summary.trim() !== ""
|
||||
|
||||
@@ -2324,7 +2324,12 @@ export async function mount(root) {
|
||||
// Phase 122 (task 04): an image row gets the FIXED 48px thumbnail
|
||||
// box before the path link (the .kb-doc-path flex wrapper — the
|
||||
// link keeps its ellipsis). Text rows keep the bare-link cell,
|
||||
// byte-identical to pre-phase (no box at all).
|
||||
// byte-identical to pre-phase (no box at all). Phase 181 (task
|
||||
// 04, design D7): verified NON-CHANGE for SVG rows — the thumb
|
||||
// loads the same extension-less image_url and .kb-doc-thumb-img
|
||||
// pins the img to the fixed 48px box (width/height 100% +
|
||||
// object-fit: cover), so a size-less viewBox-only vector fills
|
||||
// the box like a raster; no format sniffing needed here either.
|
||||
if (d.is_image) {
|
||||
const pathWrap = document.createElement("div");
|
||||
pathWrap.className = "kb-doc-path";
|
||||
|
||||
@@ -5742,6 +5742,23 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg);
|
||||
}
|
||||
/* The vector variant (phase 181, task 04, design D6 — applied in
|
||||
document.js when doc.format === "svg", base class kept). An SVG
|
||||
carrying only a viewBox has NO intrinsic size, so without an
|
||||
explicit width the default replaced-element box (300×150) applies
|
||||
and the drawing sits small inside the full-width .doc-image card;
|
||||
width: 100% spans the card (the full-size preview the owner
|
||||
asked for). object-fit: contain protects the drawing from being
|
||||
squashed when the base rule's max-height: 70vh clamps a tall
|
||||
vector — the box letterboxes, the graphic keeps its aspect ratio.
|
||||
Static content — no animation (nothing for prefers-reduced-motion
|
||||
to still). Palette-neutral: no colour here — the base rule's --bg
|
||||
shows through (phase-08 tokens only, no new hue). */
|
||||
.doc-image-img--vector {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
/* The onerror fallback (the route's 404 — the row exists but the
|
||||
copy was lost): a muted note in the card's place-of-image; the
|
||||
description below still renders, so the doc stays readable. Phase
|
||||
|
||||
Reference in New Issue
Block a user