mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
feat(documents): preview thumbnails in the vault overview
Render a small JPEG preview for each stored document and show it at the card's leading edge, falling back to the kind icon when none exists. - Generate previews in the background on upload (new document-thumbnail pg-boss queue) plus a boot-time corpus backfill for existing documents; upload never blocks on or fails because of it. Images are downscaled via @napi-rs/canvas, a PDF's first page is rasterised then downscaled, every other type is skipped. Generation never throws. - Store the preview encrypted at rest in a 1:1 DocumentThumbnail side table (migration 0236) so the list/detail queries that omit the original blob never drag the preview either. Same AES-256-GCM string codec + fail-closed decrypt as the content index; wired into the key-rotation walk. - Serve it through an owner-scoped GET /api/documents/inbound/[id]/thumbnail (auth + module gate + userId-scoped query + fail-closed decrypt), always image/jpeg + nosniff + private, no-store; 404 on miss. The card renders an authed lazy <img>, onError falls back to the icon. - Add hasThumbnail to the document DTO so the card only fetches a preview that exists. Also drop the "search looks inside your documents" caption from the overview (the index-all prompt stays) and the per-card searchable marker; both were noise. Remove the now-unused i18n keys across all six locales.
This commit is contained in:
1 parent
54c78b527e
commit
ee76672798
34 files changed
+1342
-160
No files matched your search
@@ -250,12 +250,6 @@ test.describe("document vault — AI assist + content search", () => {
|
||||
await expect(card).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page).toHaveURL(new RegExp(`q=${CONTENT_BODY_WORD}`));
|
||||
|
||||
// The matched card advertises its searchable body.
|
||||
const searchable = page
|
||||
.locator('[data-slot="document-card"]', { hasText: "Radiology note" })
|
||||
.locator('[data-slot="document-searchable"]');
|
||||
await expect(searchable).toBeVisible();
|
||||
|
||||
// Sanity: the word really is absent from the short fields (server-side
|
||||
// confirmation the union — not an ILIKE — did the work).
|
||||
const viaApi = await page.request.get(
|
||||
@@ -365,7 +359,7 @@ test.describe("document vault — AI assist + content search", () => {
|
||||
|
||||
test("a vision-indexed document surfaces the AI-read provenance", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
}) => {
|
||||
// CONTENT_DOC_ID is seeded with source "vision" (an AI provider read it).
|
||||
// No mocks: the real list/detail GET threads the provenance through.
|
||||
await page.goto(`/documents?doc=${CONTENT_DOC_ID}`);
|
||||
@@ -377,24 +371,6 @@ test.describe("document vault — AI assist + content search", () => {
|
||||
const status = sheet.locator('[data-slot="content-search-status"]');
|
||||
await expect(status).toHaveAttribute("data-state", "ai-read");
|
||||
await expect(status).toHaveText(/Read by AI/);
|
||||
|
||||
// The timeline card carries the same AI-read marker — desktop only. The
|
||||
// vault timeline is virtualized; on the narrow mobile viewport the seeded
|
||||
// card renders outside the initial virtual window (not in the DOM), so a
|
||||
// card-level attribute read there tests the virtualizer, not the marker.
|
||||
// The marker's render logic is viewport-independent and covered by the SSR
|
||||
// card test + this desktop assertion; mobile provenance is already proven
|
||||
// by the status pill above.
|
||||
if (testInfo.project.name !== "chromium-mobile") {
|
||||
await page.keyboard.press("Escape");
|
||||
const marker = page
|
||||
.locator('[data-slot="document-card"]', { hasText: "Radiology note" })
|
||||
.locator('[data-slot="document-searchable"]')
|
||||
.first();
|
||||
await expect(marker).toHaveAttribute("data-source", "ai-read", {
|
||||
timeout: 20_000,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── (f) Text-mode refuses a non-image before any OCR/upload ──────────────
|
||||
|
||||
Reference in new issue
Block a user