Commit Graph

42 Commits

Author SHA1 Message Date
c6cc9b7146 feat: show line number in search results using match position 2026-08-01 19:29:57 +08:00
2b287ccb35 fix: crop search snippets to 30 words around match 2026-08-01 19:27:07 +08:00
7d4c7eea95 feat: add /api/files static serving and HTML iframe preview 2026-08-01 19:26:26 +08:00
e059734cd9 fix: set default meilisearch key so auth is consistent without env override 2026-08-01 19:09:43 +08:00
c8a27888f6 fix: remove lru_cache from meilisearch SDK factory to avoid stale client 2026-08-01 19:04:17 +08:00
0da48660f4 fix: apply sidebar class for fixed width and document-editor flex layout for scrolling 2026-08-01 18:59:44 +08:00
66514df896 fix: replace global with globalThis in test setup to fix TS2304 build error 2026-08-01 18:55:30 +08:00
ec7368ee00 chore: ignore Vite timestamp build artifacts 2026-08-01 18:50:29 +08:00
08a1211341 feat: favicon, sidebar tooltip, fix Meilisearch auth and test warnings
- Add SVG favicon using pine/paper design tokens; wire into index.html
- Add SidebarLabel component with ResizeObserver overflow detection and
  CSS-only tooltip (pine-deep bg, IBM Plex Mono, 0.08s fade, no delay)
- Fix Meilisearch invalid_api_key: default api key to None instead of ''
  so no Authorization header is sent when auth is disabled
- Stub ResizeObserver in test setup for jsdom compatibility
- Wrap async renders in act() and advance fake timers inside act() to
  eliminate all act() warnings in App.test.tsx
2026-08-01 18:44:32 +08:00
39761b4ce7 fix: lock sidebar width and add rendered markdown preview
Sidebar overflowed horizontally on long file/folder names, which broke
vertical scrolling too — locked width with min/max-width plus
overflow-x: hidden, and truncate folder names with ellipsis.

Documents now open in a rendered preview (marked + DOMPurify) by
default, with an 编辑/预览 toggle to switch to the CodeMirror source view.
2026-08-01 18:30:50 +08:00
954b9bede4 fix: add image preview and replace Monaco with CodeMirror 6
Markdown and image previews were rendering blank because
@monaco-editor/react loads its core assets from cdn.jsdelivr.net at
runtime, which the deployment server's network can't reach. Switching
to @uiw/react-codemirror removes the CDN dependency entirely (all
assets ship in the build) and cuts bundle size from ~2.5MB to ~770KB.

Also add proper image/* content-type detection so images render via
<img> instead of being force-decoded as editor text.
2026-08-01 18:12:19 +08:00
542361c63a fix: natural-sort file tree, indent nested folders, fix editor layout
Also add a reindex endpoint and button to rebuild the Meilisearch
index from files already on disk, without needing to re-upload them.
2026-08-01 17:32:32 +08:00
4a6fdaf994 feat: support uploading entire folders
Adds a folder picker (webkitdirectory) and drag-and-drop directory
support (File System Entries API), preserving relative paths via
a new optional relative_path field on the upload endpoint.
2026-08-01 16:30:29 +08:00
4c19fad561 refactor: replace Garage/S3 storage with local filesystem storage
Single-node deployment doesn't benefit from S3 abstraction overhead;
LocalFileClient keeps the same interface DocumentService depends on,
backed by a STORAGE_DIR volume mount instead of a separate Garage service.
2026-08-01 16:30:02 +08:00
d2d046802b chore: stop tracking storage/, moved out as standalone garage deployment
Garage storage is now managed independently at ../dochub-storage,
decoupled from this repo to avoid confusion with the app stack.
2026-08-01 14:25:18 +08:00
360e9e5a14 refactor: rename all minio references to garage, drop minio SDK for boto3
The backend no longer mentions MinIO anywhere: MinioClient -> GarageClient
(minio_client.py -> garage_client.py), MINIO_* env vars -> GARAGE_*, and
the `minio` Python package is replaced with `boto3` (AWS's generic S3
client) since Garage is the only storage backend this project targets
now. S3 API semantics are unchanged, so document_service.py's behavior
is identical — this is a naming and dependency swap, not a logic change.

Also updates test fixtures/names (FakeMinioClient -> FakeGarageClient,
FakeMinioSDK -> FakeS3SDK) and storage/README.md, which no longer frames
Garage as a MinIO replacement (that migration note has served its purpose
now that no MinIO reference remains in the codebase).

Backend: 54/54 tests passing. Frontend: 38/38 tests passing (unaffected,
verified for regressions since it talks to the backend only through the
unchanged REST API).
2026-08-01 10:51:49 +08:00
0f0b7e9d32 feat: add Garage as self-hosted S3-compatible storage, replacing MinIO
MinIO's community edition has been trimming features and its licensing
direction has grown less predictable. Garage (AGPL, purpose-built for
self-hosting) is a drop-in S3-compatible replacement — no changes needed
to backend/app/minio_client.py, which talks to the S3 API generically.

Deployed independently from the app stack (storage/docker-compose.yml),
same separation MinIO had. Single-node config (replication_factor = 1)
matching this project's learning/small-team scope. init-garage.sh handles
the one-time layout assignment + bucket creation Garage requires that
MinIO didn't (MinIO auto-creates buckets via API; Garage needs an
explicit `garage bucket create` step).
2026-08-01 09:43:08 +08:00
b13810d68b feat: add docker-compose.yml for backend + frontend + meilisearch
MinIO stays out of this compose file per the design spec — it's deployed
independently, with MINIO_ENDPOINT overridable via env for wherever it
actually lives. Meilisearch uses the official image directly (no
Dockerfile needed); frontend's VITE_API_BASE_URL is left empty since
nginx.conf already proxies /api/ to the backend service.
2026-08-01 09:25:11 +08:00
a6b04c1538 chore: stop tracking docs/ (spec and plan are local-only artifacts) 2026-08-01 09:20:18 +08:00
9ef22f9f0a Fix wave 2: CORS default, path traversal, delete UI, shared clients, bucket check
- Default CORS_ORIGINS to http://localhost:5173 instead of "*" (still overridable)
- Reject ".." and leading "/" in document keys/paths across get/save/delete/upload,
  mapped to HTTP 400 via new InvalidPathError
- Add a delete button to DocumentEditor wired to deleteDocument + onDeleted,
  giving the existing DELETE API an actual UI entry point
- Share a single lru_cache'd Meilisearch (and Minio) SDK client instance instead
  of constructing duplicates in dependencies.py
- Add a startup check that creates the MinIO bucket if missing, tolerating
  MinIO being unreachable at boot without crashing the app
2026-08-01 09:15:16 +08:00
911d814a2f feat: wire approved archive-room prototype styling into React components
Adds frontend/src/styles.css extracted from the confirmed prototype
(paper/ink/pine palette, Fraunces/Inter/IBM Plex Mono type system,
catalog-tag motif) and makes the minimal JSX adjustments needed for
each component to pick up the styling: wordmark heading, search icon,
upload button icon + modal header/footer, folder chevrons + file
icons, doc-path + save-status in the editor, catalog tags and
no-results treatment in search results.

Also adds .gitignore (venv/node_modules/dist/pycache) and commits the
frontend package-lock.json that was previously untracked.
2026-08-01 09:05:44 +08:00
7e218b5738 fix: repair build, preserve unsaved edits on save failure, fix search/upload UX gaps
- client.test.ts: use globalThis.fetch instead of global.fetch and drop the
  unused describe import, fixing the broken `npm run build`
- DocumentEditor: separate load-error (replaces view) from save-error (shown
  inline, editor and unsaved content stay mounted so a failed save no longer
  destroys in-progress edits); expose dirty state via onDirtyChange
- App: prompt via window.confirm before switching documents while dirty;
  increment a refreshKey on successful upload so FileExplorer re-fetches the
  tree; track isSearching so the "no results" overlay doesn't flash during
  the search debounce window
- FileExplorer: accept a refreshKey prop to force a root-listing re-fetch
- SearchBar: convert to a controlled component (value prop) so App can clear
  the input after jumping to a search result, fixing input/state desync
- UploadButton: surface upload failures inline instead of swallowing the
  rejected promise, and keep the modal open for retry
2026-08-01 08:59:49 +08:00
13789cc0b4 fix: remove duplicate /health route, escape extracted text, validate uploads, handle binary decode
- main.py: remove duplicate /health endpoint definition
- text_extract.py: HTML-escape extracted plain text before indexing so raw
  markup in uploaded documents can't render as live HTML via search snippets
  (stored XSS fix)
- document_service.py: base64-encode content when UTF-8 decoding fails
  instead of raising UnicodeDecodeError (500) on binary files; add upload
  validation for file extension (allowlist) and size (10MB max)
- routers/documents.py: map new validation errors to HTTP 400 with a clear
  detail message instead of letting them 500
- add/extend tests covering escaping, binary get_document, and upload
  validation rejection + acceptance paths
2026-08-01 08:59:34 +08:00
ad16c1732d feat: wire FileExplorer, DocumentEditor, search and upload into App 2026-08-01 02:55:58 +08:00
90f3784ffa feat: add UploadButton with click-to-select and drag-and-drop 2026-08-01 02:54:05 +08:00
10b2b575d9 feat: add debounced SearchBar and highlighted SearchResults 2026-08-01 02:51:55 +08:00
e341a56fb2 feat: add DocumentEditor with dirty tracking and save 2026-08-01 02:47:29 +08:00
020ddac994 feat: add FileExplorer with lazy-loaded folder tree 2026-08-01 02:45:35 +08:00
e4dbd34604 feat: add typed API client for documents, upload and search 2026-08-01 02:43:32 +08:00
f8e3605ba9 feat: scaffold frontend project with Vite, Vitest and Docker build 2026-08-01 02:40:18 +08:00
448a5cbe56 feat: wire documents and search routers into FastAPI app with e2e smoke test 2026-08-01 02:36:58 +08:00
d758500548 feat: add search route forwarding queries to SearchClient 2026-08-01 02:35:00 +08:00
6633cab98e chore: wire documents router into main.py for testing 2026-08-01 02:32:51 +08:00
5b4bc4e719 feat: add documents router for list/get/save/delete/upload 2026-08-01 02:32:42 +08:00
6a286d422d feat: add DocumentService coordinating MinIO writes and search indexing 2026-08-01 02:28:58 +08:00
c931b870ed feat: add SearchClient wrapper for index/delete/search operations 2026-08-01 02:25:48 +08:00
f4126ff3ec feat: add MinioClient wrapper for list/get/put/delete object operations 2026-08-01 02:24:15 +08:00
2a7a197b95 feat: add stdlib-only markdown and html text extraction for indexing 2026-08-01 02:21:38 +08:00
f9c612927a feat: scaffold FastAPI backend with config and health endpoint 2026-08-01 02:15:01 +08:00
954f7ac367 Restructure backend plan to FastAPI's official routers/ + dependencies.py layout 2026-08-01 02:01:28 +08:00
ba7ee21c28 Add DocHub implementation plan 2026-08-01 01:54:16 +08:00
763fbb77a8 Add DocHub design spec 2026-08-01 00:22:16 +08:00