A personal information capture and retrieval system: anything you encounter and want to find again should be easy to capture, understand, connect, and retrieve.
Note
This repository is currently in Stage 11 (Secure Binary File & PDF Capture Foundation). LENS now supports secure binary document upload and asynchronous ingestion for PDF files. Uploaded documents are stored in local MinIO (S3-compatible object storage), validated with strict magic byte and size enforcement, indexed through bounded page-by-page text extraction, partitioned into deterministic chunks, embedded with 1536-dimensional vector representations, and made instantly searchable via the unified hybrid search API. OCR and image document processing remain explicitly deferred.
Modern knowledge work suffers from severe information fragmentation:
- Articles, documentation, code snippets, GitHub repositories, and thoughts are scattered across dozens of disconnected tools and browser tabs.
- Bookmarks and traditional note-taking apps turn into "write-only" archives where saved content is rarely discovered when needed.
- Keyword-based search breaks down when users remember concepts, context, or visual cues rather than exact strings.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ CAPTURE │ ──> │ UNDERSTAND │ ──> │ CONNECT │ ──> │ RETRIEVE │
│ Web / Mobile │ │ Extract/Meta │ │ Graph/Vector │ │ Hybrid Search│
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
- Capture: Seamless, low-friction entry point across web (browser extension / web dashboard) and mobile (share sheet / quick capture) for links, text, media, and integrations.
- Understand: Automated extraction of content, summaries, metadata, and structural representations.
- Connect: Link inference, topic clustering, and semantic vector embedding generation.
- Retrieve: High-precision hybrid search (combining exact keyword matching and semantic similarity) resurfacing the right context at the right time.
- Omni-Capture Interface: Unified ingestion for URLs, raw notes, GitHub repositories, and media.
- Intelligent Processing Pipeline: Asynchronous background jobs handling metadata extraction, text chunking, and embedding generation.
- Hybrid Search Engine: Combined lexical (PostgreSQL full-text) and semantic (pgvector) query execution.
- Multi-Platform Access: Responsive Next.js web application and cross-platform Flutter mobile application.
- GitHub Integration: Ingest starred repositories, releases, and issue notes via OAuth and webhooks.
- Secure Storage: Encrypted JWT authentication with private S3-compatible object storage for captured media and raw payloads.
graph TD
subgraph Clients [Clients]
Web["Web Application (Next.js + TypeScript)"]
Mobile["Mobile Application (Flutter)"]
end
subgraph API_Gateway [Services]
API["FastAPI Backend (REST API)"]
Worker["Background Worker (Python)"]
end
subgraph Infrastructure [Data & Infrastructure]
PG[("PostgreSQL + pgvector")]
Redis[("Redis (Cache & Task Queue)")]
S3[("S3-Compatible Object Storage")]
end
Web -->|HTTP / REST| API
Mobile -->|HTTP / REST| API
API -->|Read / Write Metadata & Vectors| PG
API -->|Enqueue Jobs / Cache| Redis
API -->|Store Raw Files / Media| S3
Worker -->|Consume Tasks| Redis
Worker -->|Update Embeddings & Status| PG
Worker -->|Access Artifacts| S3
lens/
├── apps/
│ ├── web/ # Next.js web application [PLANNED]
│ └── mobile/ # Flutter mobile application [PLANNED]
├── services/
│ ├── api/ # FastAPI REST API service [PLANNED]
│ └── worker/ # Python background processing worker [PLANNED]
├── packages/ # Shared libraries and configurations [PLANNED]
├── infra/ # Docker, local development, and deployment specs [PLANNED]
├── docs/ # Engineering and architectural documentation
│ └── architecture.md
├── .editorconfig # Consistent cross-editor formatting rules
├── .gitignore # Comprehensive ignore rules for all tech stacks
└── README.md # Repository overview and development guide
| Layer | Technology | Status |
|---|---|---|
| Web Frontend | Next.js (React), TypeScript | Planned |
| Mobile Frontend | Flutter, Dart | Planned |
| Backend API | FastAPI, Python 3.11+ | Active Core & Capture Ingestion API (services/api/) |
| Background Worker | Python 3.11+, BeautifulSoup4, HTTPX | Active Ingestion & Extraction Worker (services/worker/) |
| Primary Database | PostgreSQL 16 + pgvector extension |
Active Models & Migrations (services/api/alembic/) |
| Cache & Queue | Redis 7.4 (Reliable List Queue via BLMOVE) |
Active Job Transport (infra/docker-compose.yml) |
| Object Storage | S3-Compatible (MinIO) | Infra Configured (infra/docker-compose.yml) |
| Authentication | JWT (Initial) | Planned |
| Integrations | GitHub OAuth & Webhooks | Planned |
| Local Containerization | Docker & Docker Compose | Active / Tested |
Before setting up application components in subsequent stages, verify that your workstation has the following tooling installed:
- Node.js: v20+ LTS recommended (v26.5.0 verified)
- Node Package Manager:
pnpm/npm/bun - Python: v3.11.9+ with
pip - Flutter SDK: v3.20+ (v3.41.7 verified) & Dart SDK (v3.11.5 verified)
- Android SDK / ADB: For mobile device emulation and debugging
- Docker & Docker Compose: For running local databases, caches, and object storage
- Git: v2.40+ (v2.55.0 verified)
To run the local backing services (PostgreSQL with pgvector, Redis, and MinIO), follow these steps:
-
Start Docker Desktop: Ensure the Docker daemon is running on your host machine.
-
Copy
.env.exampleto.env:cp .env.example .env
[!CAUTION] CRITICAL RULE: The
.envfile contains local secrets and must NEVER be committed to version control. The repository's.gitignoreexplicitly prevents tracking this file. -
Fill Only Required Values for Local Development: For local development of infrastructure, the default values provided in
.env.examplefor PostgreSQL (lens_user,lens_password,lens), Redis (localhost:6379), and MinIO (minioadmin/minioadmin) are functional defaults for local usage.Credentials NOT required at this stage (features are planned and not yet implemented):
JWT_SECRET,JWT_ACCESS_TOKEN_EXPIRE_MINUTES,JWT_REFRESH_TOKEN_EXPIRE_DAYS— Authentication service is not yet built.GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,GITHUB_WEBHOOK_SECRET— GitHub integration is not yet implemented.AI_PROVIDER,AI_API_KEY— Embedding and LLM pipelines are not yet implemented.
-
Start Infrastructure with Docker Compose:
docker compose -f infra/docker-compose.yml up -d
-
Check Service Health:
docker compose -f infra/docker-compose.yml ps
All three services should report healthy:
lens-postgres(PostgreSQL 16 with pgvector on port5432)lens-redis(Redis 7.4 on port6379)lens-minio(MinIO S3 API on port9000, Web Console on port9001)
-
Stop Infrastructure When Finished:
docker compose -f infra/docker-compose.yml down
Note: Named volumes (
lens_postgres_data,lens_redis_data,lens_minio_data) are preserved across stops. Add-vonly if you explicitly intend to delete local data.
The core API service provides the backend foundation for LENS:
-
Location:
services/api/ -
Python Environment Setup:
cd services/api python -m venv .venv # Windows PowerShell .\.venv\Scripts\Activate.ps1 # Linux / macOS source .venv/bin/activate
-
Dependency Installation:
pip install -r requirements.txt
-
Prerequisites: Ensure local infrastructure (PostgreSQL and Redis) is running via Docker Compose (
docker compose -f infra/docker-compose.yml up -d). -
Start API Locally:
# From services/api/ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 -
Verify Health:
curl http://localhost:8000/health
Expected response when all dependencies are healthy:
{"status":"ok","api":"ok","database":"ok","redis":"ok"}If PostgreSQL or Redis is unreachable, the endpoint returns
503 Service Unavailablewithstatus: "degraded"and identifies the failing component without exposing internal credentials or tracebacks. -
Current API Scope:
- Foundational application structure (
app/core,app/db,app/health,app/captures). - Configuration loader (
pydantic-settings). - Asynchronous SQLAlchemy engine (
asyncpg) and Redis client. - Active connectivity health checks (
GET /health). - Core capture ingestion endpoints (
POST /captures,GET /captures,GET /captures/{capture_id}). - NOT YET IMPLEMENTED: Authentication, user management, search endpoints, embeddings, file ingestion, or external integrations.
- Foundational application structure (
The database layer utilizes modern SQLAlchemy 2.x declarative models and Alembic for asynchronous PostgreSQL schema evolution:
-
Core Domain Models (
services/api/app/db/models.py):Capture: The primary record representing any ingested item. Fields include UUID primary key,source_typeenum (url,text,image,pdf,file,github), optional title, original URL, content text, mime type, external source ID, and timestamps.ContentChunk: Searchable content chunks belonging to a Capture. Features foreign key withCASCADEdeletion,chunk_index, text content, and aVector(1536)column for future dense vector embeddings.SourceMetadata: Flexible 1-to-1 extension table storing source-specific metadata as PostgreSQLJSONBwith foreign keyCASCADEdeletion.
-
Running Migrations: From
services/api/with the virtual environment activated:# Upgrade database to latest migration alembic upgrade head # Roll back the most recent migration alembic downgrade -1 # Create a new autogenerated revision alembic revision --autogenerate -m "migration_description"
-
Current Schema Scope:
- Foundational data structures and storage capability for captures, chunks, vectors, and metadata.
- Embeddings column is a storage-only placeholder; AI embedding calculation is not yet implemented.
- User accounts, authentication schemas, queue persistence, and search APIs remain planned for upcoming stages.
Stage 5 implements the first core product capability of LENS: ingesting URLs and raw notes/text payloads into durable storage with optional source metadata.
| Method | Path | Description | Success Status |
|---|---|---|---|
POST |
/captures |
Ingest and persist a new URL or text capture atomically | 201 Created |
GET |
/captures |
Retrieve paginated captures ordered newest first (created_at DESC) |
200 OK |
GET |
/captures/{capture_id} |
Retrieve a single capture by UUID along with source metadata | 200 OK |
Ingests a new capture item. Validation strictly enforces:
source_type: Must be"url"or"text". Unsupported types ("image","pdf","file","github") are rejected until dedicated ingestion flows are built.- URL Captures:
original_url: Required, must be a valid absolute HTTP or HTTPS URL (schemes likefile://orjavascript:are rejected).title,content,mime_type,source_id,metadata: Optional.
- Text Captures:
content: Required, must not be empty or whitespace-only.title,metadata: Optional.
- Atomicity: If
metadatais provided,CaptureandSourceMetadataare persisted in a single atomic database transaction. If metadata persistence fails, the entire transaction rolls back.
Example Request — URL Capture:
curl -X POST http://localhost:8000/captures \
-H "Content-Type: application/json" \
-d '{
"source_type": "url",
"original_url": "https://fastapi.tiangolo.com",
"title": "FastAPI Documentation",
"metadata": {"tags": ["python", "api"], "reading_priority": "high"}
}'Example Request — Text Capture:
curl -X POST http://localhost:8000/captures \
-H "Content-Type: application/json" \
-d '{
"source_type": "text",
"title": "Architecture Thoughts",
"content": "Decoupling ingestion from chunking ensures fast capture response times."
}'Example Response (201 Created):
{
"id": "1804f326-fa42-4916-834c-687f8f9024f0",
"source_type": "url",
"title": "FastAPI Documentation",
"original_url": "https://fastapi.tiangolo.com",
"content": null,
"mime_type": null,
"source_id": null,
"created_at": "2026-10-02T10:30:00Z",
"updated_at": "2026-10-02T10:30:00Z"
}Lists captures using deterministic pagination ordered newest-first (created_at DESC, id DESC). Does not load unneeded relationships.
Query Parameters:
limit: Integer, default20, minimum1, maximum100.offset: Integer, default0, minimum0.
Example Request:
curl "http://localhost:8000/captures?limit=10&offset=0"Example Response (200 OK):
{
"items": [
{
"id": "1804f326-fa42-4916-834c-687f8f9024f0",
"source_type": "url",
"title": "FastAPI Documentation",
"original_url": "https://fastapi.tiangolo.com",
"content": null,
"mime_type": null,
"source_id": null,
"created_at": "2026-10-02T10:30:00Z",
"updated_at": "2026-10-02T10:30:00Z"
}
],
"limit": 10,
"offset": 0,
"total": 1
}Retrieves a specific capture by UUID. If source metadata exists for the capture, it is included in the response.
Behavior:
- Returns
200 OKwith capture details andmetadata: dict | null. - Returns
404 Not Foundif the UUID is valid but does not exist. - Returns
422 Unprocessable Entityif thecapture_idis not a valid UUID.
Example Request:
curl http://localhost:8000/captures/1804f326-fa42-4916-834c-687f8f9024f0Example Response (200 OK):
{
"id": "1804f326-fa42-4916-834c-687f8f9024f0",
"source_type": "url",
"title": "FastAPI Documentation",
"original_url": "https://fastapi.tiangolo.com",
"content": null,
"mime_type": null,
"source_id": null,
"created_at": "2026-10-02T10:30:00Z",
"updated_at": "2026-10-02T10:30:00Z",
"metadata": {
"tags": ["python", "api"],
"reading_priority": "high"
}
}Stage 6 introduces the first asynchronous ingestion worker to extract readable HTML content, normalize text, and partition it into deterministic chunks stored in content_chunks.
The worker decouples capture persistence from slow and untrusted network operations (fetching remote URLs, HTML parsing, text normalization, and chunking).
Client
↓
POST /captures (source_type = "url")
↓
PostgreSQL (Capture created, processing_status: "pending")
↓
Redis Job Enqueue ("lens:jobs:url_ingest")
↓ [API returns HTTP 201 immediately]
Worker (BLMOVE -> "lens:jobs:url_ingest:processing")
↓
SSRF-Safe Fetcher (validates scheme, host, resolved IPs, redirects, 2MB limit, timeouts)
↓
HTML Extraction (BeautifulSoup, strips scripts/styles/nav/ads)
↓
Text Normalization (whitespace collapse, line endings)
↓
Deterministic Chunking (1000 chars, 150 char overlap)
↓
Embedding Generation (OpenAI text-embedding-3-small or Deterministic Test Provider, 1536-dim)
↓
PostgreSQL Persistence (idempotently deletes old chunks, inserts ContentChunks + embedding atomically)
↓
Capture Completed (processing_status: "completed", processed_at: UTC timestamp)
Captured items move through deterministic lifecycle states tracked on the captures table:
pending: Initial state for URL captures upon API creation. Indicates the job is queued in Redis.processing: Worker has acquired the job and is currently fetching, extracting, chunking, or embedding content.completed: Webpage successfully fetched, parsed, normalized, partitioned into chunks, and embedded with 1536-dimensional vectors. (Text captures start directly ascompletedsince no ingestion is required).failed: Ingestion failed due to SSRF policy violation, non-HTML content, response size limit exceeded (>2MB), request timeout, insufficient readable text, or upstream embedding provider errors.- The
processing_errorcolumn records a safe, sanitized diagnostic message. Internal stack traces, raw webpage content, database credentials, API keys, and authorization headers are strictly excluded.
- The
-
Job Contract:
{ "type": "capture.url_ingest", "version": 1, "capture_id": "1804f326-fa42-4916-834c-687f8f9024f0" } -
At-Least-Once Delivery: The worker uses
BLMOVEto atomically move jobs from the main queue (lens:jobs:url_ingest) to an in-progress queue (lens:jobs:url_ingest:processing). Jobs are acknowledged and removed viaLREMonly after processing finishes. -
Idempotency & Atomic Replacement: Retrying a job automatically deletes any previously generated
ContentChunkrecords for thatcapture_idbefore inserting newly generated chunks and vectors inside a single database transaction, preventing duplicate or partial chunk accumulation.
The worker is designed to run easily in both continuous background mode and single-job test mode:
# Activate virtual environment
cd services/worker
# Run worker in continuous listening mode
..\api\.venv\Scripts\python.exe -m worker.main
# Run worker in single-job mode (processes one job from the queue and exits)
..\api\.venv\Scripts\python.exe -m worker.main --onceStage 7 introduces the baseline search engine combining PostgreSQL native full-text search with pgvector cosine distance search.
Executes deterministic hybrid search across captured content and titles.
Query Parameters:
q(string, required): Search query string (1 to 500 characters). Empty or whitespace-only queries return422 Unprocessable Entity.limit(integer, optional): Maximum number of matching captures to return (1 to 50, default10).
Hybrid Ranking Formula:
-
Normalized Lexical Score:
$S_{lex} = \min(1.0, \text{ts_rank_cd} / 0.5)$ -
Normalized Semantic Score:
$S_{sem} = \max(0.0, 1.0 - \text{cosine_distance})$ -
Combined Score:
$S_{hybrid} = W_{lex} \cdot S_{lex} + W_{sem} \cdot S_{sem}$ (default$W_{lex} = 0.5, W_{sem} = 0.5$ ) -
Stable Tie-Breaking: Ordered by
hybrid_score DESC, chunk_index ASC, id ASC.
Result Grouping: Multiple matching chunks belonging to the same capture are grouped under that capture, with matching chunks ordered by individual chunk hybrid score descending.
Example Request:
curl "http://localhost:8000/search?q=fastapi+async+database&limit=5"Example Response (200 OK):
{
"query": "fastapi async database",
"limit": 5,
"count": 1,
"results": [
{
"capture_id": "1804f326-fa42-4916-834c-687f8f9024f0",
"title": "FastAPI Async Documentation",
"source_type": "url",
"original_url": "https://fastapi.tiangolo.com",
"hybrid_score": 0.824,
"lexical_score": 0.720,
"semantic_score": 0.928,
"matching_chunks": [
{
"chunk_id": "528f8045-8141-4ee6-857e-5264b38bfd2e",
"chunk_index": 0,
"excerpt": "FastAPI is a modern, fast (high-performance), web framework for building APIs with Python...",
"lexical_score": 0.720,
"semantic_score": 0.928,
"hybrid_score": 0.824
}
]
}
]
}If the embedding provider is unavailable, times out, or no API key is configured, search automatically falls back to lexical-only search without throwing 500 errors or fabricating semantic scores.
The LENS web application provides a high-density, keyboard-friendly personal information workspace for capturing, tracking, and retrieving notes and web links.
- Framework: Next.js 16 (App Router, Turbopack) + React 19 + TypeScript 5
- Styling: TailwindCSS v4 with dark workspace aesthetic
- Testing: Vitest + JSDOM + Testing Library (31 unit & integration tests)
- API Client: Centralized, typed API client (
src/lib/api-client.ts) with error normalization, timeout handling, and request abort semantics.
/captures: Main knowledge list with newest-first ordering, pagination, source badges, status indicators, and bounded live refresh./captures/new: Capture creation supporting both web URLs and multi-line text notes with client validation and immediate redirect./captures/[id]: Detailed capture inspector showing status, timestamps, extracted text chunks, source metadata, and safe backend error diagnostics./search: Hybrid search interface with query synchronization to URL params, race-condition safety, and full score breakdowns (Hybrid,Semantic,Lexical).
# Navigate to web workspace
cd apps/web
# Install dependencies
npm install
# Start local Next.js dev server on http://localhost:3000
npm run dev
# Run Vitest test suite (31 tests)
npm run test
# Run TypeScript type check
npm run typecheck
# Run ESLint
npm run lint
# Build production bundle
npm run buildConfiguration:
NEXT_PUBLIC_API_BASE_URL: Base URL of the FastAPI backend service (defaults tohttp://localhost:8000for local development).- The browser client strictly never receives database connection strings, Redis passwords, or OpenAI API keys.
- Pending (
pending): "Waiting to process" (indigo badge). - Processing (
processing): "Processing" (blue badge with spinner). - Completed (
completed): "Ready" (emerald badge with checkmark). - Failed (
failed): "Processing failed" (rose badge with error banner showing safe backend diagnostic message). - Bounded Polling: Pending or processing captures poll the backend at 3-second intervals for at most 10 attempts (30 seconds max). Polling stops immediately upon reaching a terminal state (
completedorfailed) or when the user navigates away.
- XSS Defense: Captured webpage content and text notes are rendered strictly through standard React string interpolation as text nodes;
dangerouslySetInnerHTMLis prohibited. - URL Injection Defense: External URLs pass through
sanitizeUrl(), which parses URLs vianew URL()and rejectsjavascript:,data:,vbscript:, or relative links. - Error Sanitization: Server-side 500 errors and tracebacks are intercepted and replaced with user-safe status summaries.
Note
- Mobile Application is implemented in Stage 9 (
apps/mobile): The Flutter mobile app has been built and verified on Android. - Authentication is NOT implemented yet: Endpoints remain unauthenticated for local development.
- Binary files, PDFs, and OCR are NOT processed yet: Object storage / S3 / MinIO uploads remain planned.
- External integrations are NOT implemented yet: GitHub synchronization and OAuth remain planned.
The LENS mobile application is a Flutter Android client designed for frictionless personal capture on physical Android phones and emulators.
- Framework: Flutter 3.41+ (Channel stable) / Dart 3.11+
- Platform: Android SDK 36.1.0 (minSdkVersion 24, targetSdkVersion 35)
- Design System: Material 3 dark workspace theme with unified slate/emerald/amber/rose palette
- Network Layer: Centralized
ApiClientbacked bypackage:httpwith timeout guards, error normalization, and configurable base URLs viaApiConfigandSharedPreferences. - Testing: 13 unit and widget tests covering models, serialization, error masking, formatters, and screen widgets.
- Captures Screen (
lib/screens/captures_screen.dart):- Paginated capture feed ordered newest-first with pull-to-refresh.
- Source type indicators (URL vs Note) and semantic processing status badges.
- Live server connection status banner with fast configuration access.
- New Capture Screen (
lib/screens/new_capture_screen.dart):- Multi-tab capture creation for web URLs and text notes.
- Client-side validation: URL format validation and non-empty text validation.
- Immediate optimistic navigation to the capture inspector upon submission.
- Capture Detail Screen (
lib/screens/capture_detail_screen.dart):- Full capture inspector displaying processing status, timestamps, and extracted content chunks.
- Bounded polling: Automatically polls the backend every 3 seconds for active captures (
pendingorprocessing) up to 10 attempts (30s max), halting on completion or screen disposal. - User-safe error presentation with copyable diagnostic messages (sensitive backend internals masked).
- Search Screen (
lib/screens/search_screen.dart):- Fast hybrid search over captured notes and links.
- Race-condition safety: Sequential request tracking ensures stale asynchronous queries do not overwrite fresh results.
- Transparent relevance scoring: Displays overall hybrid score alongside semantic and lexical sub-scores.
- Server Config Dialog (
lib/widgets/server_config_dialog.dart):- In-app runtime configuration dialog accessible from the app bar.
- Preset shortcuts for Android Emulator (
10.0.2.2:8000), USB ADB Reverse (127.0.0.1:8000), and Local Wi-Fi LAN (10.50.109.63:8000). - Active "Test Connection" ping against
GET /healthwith immediate latency and connectivity feedback.
Android prevents cleartext HTTP traffic by default. LENS handles this securely:
- Scoped Cleartext Traffic:
android:usesCleartextTraffic="true"is enabled strictly insideandroid/app/src/debug/AndroidManifest.xml. Release builds remain secure and HTTPS-only. - Connecting a Physical Android Phone:
-
USB Reverse Proxy (Recommended for Enterprise/Campus Wi-Fi): Connect phone via USB with USB debugging enabled, then execute:
adb reverse tcp:8000 tcp:8000
The phone can then access the local FastAPI server directly at
http://127.0.0.1:8000regardless of router client-isolation. -
Wi-Fi LAN Connection: Ensure the phone is on the same local Wi-Fi network and connect using your workstation LAN IP:
flutter run --dart-define=API_BASE_URL=http://<YOUR_LAN_IP>:8000
-
Android Emulator: Use the preset
http://10.0.2.2:8000.
-
# Navigate to mobile workspace
cd apps/mobile
# Install Flutter dependencies
flutter pub get
# Run static analysis (0 warnings/errors required)
flutter analyze
# Run mobile test suite (13 unit and widget tests)
flutter test
# Build debug APK for testing
flutter build apk --debug
# Run directly on connected Android device or emulator
flutter run- XSS Defense: Captured webpage content and text notes are rendered strictly through standard Flutter text widgets with automatic escaping.
- URL Injection Defense: External URLs pass through
sanitizeUrl(), which rejects dangerous schemes (javascript:,data:,vbscript:) before display or launching. - Credential Isolation: Client application contains zero database credentials, Redis URLs, or LLM API keys.
- Error Sanitization: Backend error payloads are stripped of database connection strings, paths, and internal stack traces before user presentation.
Important
The following mobile capabilities are intentionally deferred to Stage 10+:
- Android System Share Sheet: "Share to LENS" intent filters and background receivers.
- Offline SQLite Queue: Local persistent storage and offline capture synchronization.
- Push Notifications: Background processing status update alerts.
- User Authentication: Login screens, token refresh, and multi-tenant isolation.
- Camera / Document Scanner: Binary image capture and on-device OCR.
Stage 11 establishes the backend foundation for binary document captures in LENS, introducing PDF ingestion as the first searchable binary format.
- Object Storage: High-performance S3-compatible MinIO container (
lens-minioonlocalhost:9000) for immutable raw document payloads. - Storage Client Abstraction: Centralized
StorageClient(app.storage) with credential isolation and secure operations (put_object,get_object,stat_object,delete_object). - Cryptographic Object Keys: Pure UUID-based internal storage paths (
captures/<capture_id>/<object_uuid>.pdf), strictly preventing path traversal and directory manipulation. - Document Processing: Asynchronous background worker consuming
capture.file_ingestjobs, extracting text with memory-boundedpypdf, chunking deterministically, and generating 1536-dimensional embeddings.
- Endpoint:
POST /captures/upload - Content-Type:
multipart/form-data - Fields:
file: Binary document file (application/pdf).title(optional): Human-readable capture title. Defaults to sanitized filename.
- Response: Standard
CaptureDetailResponse(status201 Created, initial statepending).
| Parameter | Boundary / Policy | Enforcement |
|---|---|---|
| Max File Size | 10 MB (MAX_UPLOAD_SIZE_BYTES) |
Streaming byte-count check (HTTP 413) |
| MIME Type | application/pdf |
Header inspection (HTTP 415) |
| Magic Bytes | %PDF- (0x25 0x50 0x44 0x46 0x2D) |
File header signature validation (HTTP 415) |
| Filename | Max 255 chars, no traversal | Stripped path components & sanitized basename |
| PDF Pages | Max 500 pages | Bounded pypdf page iterator |
| Extracted Text | Max 2,000,000 chars | Memory-bound guard against resource amplification |
| Scanned / Empty | Min 10 readable chars | Rejected with InsufficientContentError |
| Encrypted PDFs | Password-protected files rejected | Checked via reader.is_encrypted |
- Upload Success: Object stored in S3,
Capture(pending) &SourceMetadatasaved in PostgreSQL, job enqueued to Redis listlens:jobs:file_ingest. - Database Failure Cleanup: If database persistence fails after S3 upload, the uploaded object is immediately deleted from MinIO to prevent storage leaks.
- Queue Outage Resiliency: If Redis enqueue fails,
Captureis set tofailedwhile the S3 object is preserved, allowing safe background retry. - Worker Idempotency: Atomic chunk deletion and recreation ensures duplicate delivery never duplicates chunks or search results.
Important
The following capabilities are explicitly deferred:
- OCR & Image Recognition: Scanned, non-text PDFs and standalone images remain unsearchable in this stage.
- Mobile File Picker UI: Mobile client upload interfaces are planned for future stages.
- Presigned Public Downloads: Direct browser download links remain internal.
- Alternative Document Formats: DOCX, EPUB, and audio formats are deferred.
- Separation of Concerns: Client applications (Web, Mobile) consume standard API contracts; backend services (API, Worker) decouple ingestion from processing.
- Asynchronous by Default: Heavy tasks (embedding calculations, OCR, large link scraping) must never block API responses; they are handled via worker queues.
- Reproducible Environments: Local development dependencies and databases run in containerized environments with pinned versions.
- Resilient Data Capture: Ingestion endpoints must acknowledge receipt quickly, persist raw payloads first, and process transformations idempotently.
Caution
CRITICAL RULE: Secrets, API keys, private tokens, certificates, and .env* files MUST NEVER be committed to version control.
- All environment configurations must be supplied via local uncommitted
.envfiles or system environment variables. - Reference templates must be provided exclusively as
.env.exampleor.env.*.example. - The root
.gitignoreenforces strict ignoring of credential and environment files.