All checks were successful
Test / rust-fmt-check (pull_request) Successful in 1m35s
Test / frontend-tests (pull_request) Successful in 1m41s
Test / frontend-typecheck (pull_request) Successful in 1m43s
Test / rust-clippy (pull_request) Successful in 3m10s
Test / rust-tests (pull_request) Successful in 4m39s
PR Review Automation / review (pull_request) Successful in 4m58s
Store compressed log content and raw image bytes in SQLite so attachments are self-contained regardless of source file availability on disk. DB (migrations 020-022): - log_files.content_compressed BLOB — gzip-compressed extracted text - image_attachments.image_data BLOB — raw image bytes - Views v_log_files_with_issue and v_image_attachments_with_issue for cross-incident queries with joined issue title Rust backend: - compress_text / decompress_text helpers (flate2 rust_backend / miniz_oxide) with 100 MB decompression-bomb guard - upload_log_file*, upload_log_file_by_content store content_compressed - upload_image_attachment*, upload_paste_image store image_data - New commands: get_log_file_content, list_all_log_files (analysis.rs) - New commands: get_image_attachment_data, list_all_image_attachments (image.rs) - All commands fall back to file_path for pre-migration records Frontend: - LogFileSummary, ImageAttachmentSummary types in tauriCommands.ts - attachmentStore (Zustand) — loadAttachments, searchAttachments - History page: Issues tab (existing) + Attachments tab (new) with log/image tables, search bar, View modals, lazy thumbnails Tests: 227 Rust (+16 new), 103 frontend (+9 new), tsc clean, clippy clean Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
6.3 KiB
6.3 KiB
Ticket: Attachment DB Storage & Cross-Incident Recall
Branch: feature/attachment-db-storage-recall
Base: master
Description
Log file and image attachment records previously stored only metadata and filesystem paths, making content volatile — if the source file moved or was deleted, the attachment record became orphaned. There was also no mechanism to search or recall attachments across incidents.
This feature:
- Stores gzip-compressed log text and raw image bytes directly in the database, making attachments fully self-contained and portable.
- Surfaces a new Attachments tab on the History page for cross-incident search and recall.
- Exposes content-retrieval commands so the AI chat context can reference log content from DB on demand, with no disk dependency.
Acceptance Criteria
- Uploading a log file stores gzip-compressed text in
log_files.content_compressed(BLOB) - Uploading an image stores raw bytes in
image_attachments.image_data(BLOB) get_log_file_contentreturns decompressed text from DB; falls back to disk for pre-migration recordsget_image_attachment_datareturns base64 data URL from DB; falls back to disk for pre-migration recordslist_all_log_filesreturns cross-incident log summaries with joined issue title, supports search and issueId filterlist_all_image_attachmentsreturns cross-incident image summaries with joined issue title, supports search and issueId filter- History page shows two tabs: Issues (existing, unchanged) and Attachments (new)
- Attachments tab: Log Files section with filename, incident link, date, size, type badge, View button
- Attachments tab: Images section with 48px thumbnail, filename, incident link, date, View button
- "View" on log file → modal showing decompressed plain text
- "View" on image → modal showing full-size image
- Existing records with NULL content fall back to disk read — no breakage for pre-migration data
- All new DB changes tracked via migrations 020–022 with idempotency guarantees
- Wiki documentation updated: IPC-Commands.md and Database.md
Work Implemented
Database (src-tauri/src/db/)
| File | Change |
|---|---|
migrations.rs |
Migrations 020 (content_compressed BLOB), 021 (image_data BLOB), 022 (views v_log_files_with_issue + v_image_attachments_with_issue). Extended duplicate-column graceful handling for new ALTER TABLE migrations. |
models.rs |
Added LogFileSummary and ImageAttachmentSummary structs for lightweight cross-incident list views (no BLOB fields — content stays out of IPC). |
Rust Backend (src-tauri/src/commands/)
| File | Change |
|---|---|
analysis.rs |
Private compress_text / decompress_text helpers (flate2/miniz_oxide — pure Rust, no system binary). Updated upload_log_file and upload_log_file_by_content INSERTs to store content_compressed. New commands: get_log_file_content, list_all_log_files. |
image.rs |
Updated upload_image_attachment, upload_image_attachment_by_content, upload_paste_image INSERTs to store image_data. New commands: get_image_attachment_data, list_all_image_attachments. |
lib.rs |
Registered all 4 new commands. |
Dependencies (src-tauri/Cargo.toml)
- Added
flate2 = { version = "1", features = ["rust_backend"] }— pure-Rust gzip, portable cross-platform.
Frontend (src/)
| File | Change |
|---|---|
lib/tauriCommands.ts |
Added LogFileSummary, ImageAttachmentSummary interfaces and 4 typed command wrappers. |
stores/attachmentStore.ts |
New Zustand store: loadAttachments, searchAttachments, setSearchQuery. |
pages/History/index.tsx |
Added tab bar; extracted IssuesTab (existing content, unchanged); added AttachmentsTab with log/image tables, search, View modals, and lazy ImageThumbnail component. |
Documentation (docs/wiki/)
| File | Change |
|---|---|
IPC-Commands.md |
Documented get_log_file_content, list_all_log_files, get_image_attachment_data, list_all_image_attachments with TypeScript signatures and interface shapes. Updated upload command notes. |
Database.md |
Updated migration count (18 → 22). Documented migrations 020, 021, 022 with SQL, rationale, and usage notes. |
Testing Needed
Automated (already passing)
| Suite | Count | Status |
|---|---|---|
Rust unit tests (cargo test) |
226 | ✅ All pass |
Frontend unit tests (npm run test:run) |
103 | ✅ All pass |
TypeScript type check (tsc --noEmit) |
— | ✅ Clean |
Rust clippy (clippy -- -D warnings) |
— | ✅ Zero warnings |
Rust format (fmt --check) |
— | ✅ Clean |
New tests added:
test_compress_decompress_roundtrip,test_compress_large_text_is_smaller,test_decompress_invalid_bytes_returns_error(Rust,analysis.rs)test_get_image_attachment_data_base64_format(Rust,image.rs)test_020_log_content_compressed_column,test_021_image_data_column,test_022_attachment_views_exist,test_022_views_join_issue_title,test_020_021_idempotent(Rust,migrations.rs)- 9 attachment store tests (
tests/unit/attachmentStore.test.ts)
Manual Smoke Testing Required
-
Log upload → DB content storage
- Create issue → upload
.logfile → inspect SQLite:SELECT id, LENGTH(content_compressed) FROM log_files— verify non-NULL non-zero value
- Create issue → upload
-
Content retrieval from DB
- History → Attachments tab → Log Files → click "View" → confirm readable decompressed text appears in modal
-
Fallback for pre-migration records
- Manually
UPDATE log_files SET content_compressed = NULL WHERE id = '<id>'→ View should still load from disk path
- Manually
-
Image upload → DB byte storage
- Upload image →
SELECT id, LENGTH(image_data) FROM image_attachments— verify non-NULL
- Upload image →
-
Image display
- History → Attachments tab → Images → thumbnails should render, View → full-size image modal
-
Cross-incident search
- Create 2+ issues with different log files → Attachments tab → search by partial filename → correct files appear
-
Issue link navigation
- Click incident title in Attachments tab → navigates to correct triage page
-
Issue tab unchanged
- Verify existing Issues tab retains all functionality (search, filter, sort, open, export buttons)