Files
haopengzhan ebcaa5d72a
CI Quality Checks & Tests / Gitleaks Secret Detection (push) Failing after 1m59s
CI Quality Checks & Tests / Go Lint & Unit Tests (push) Failing after 2m26s
CI Quality Checks & Tests / Frontend Typecheck & Build (push) Successful in 2m37s
refactor(core): overhaul state transition architecture, persistent job queue, and API layer
- Implement atomic state transitions: add EntryService.PatchEntry() with EntryDiff computation and EventEntryTransition event emission.
- Add persistent SQLite task queue (JobEngine): add jobs table with crash recovery, exponential backoff retries, concurrency control, and idempotency key deduplication.
- Decompose monolithic HTTP handlers: split handlers.go into domain handlers (auth, feeds, entries, jobs, settings, rules, sse).
- Implement real-time Server-Sent Events (/api/events/stream) for multi-tab/device state synchronization.
- Fix SQLite concurrency: apply busy_timeout(10000) and journal_mode(WAL) via DSN query parameters to resolve SQLITE_BUSY (5).
- Fix AI settings & prompt loading: unmarshal JSON from ai_settings, apply configured prompt templates, and enforce strict enabled/API key validations.
- Fix undo share state: remove omitempty from ShareUUID to serialize null for state resets.
- Fix middle list counter: compute true unread/total stats from DB and clean switcher button labels.
- Adapt mobile UI for iOS: add viewport-fit=cover in index.html and calibrate safe-area insets for the bottom navigation and article reader toolbar.
- Add architectural documentation and worklog in docs/009_worklog.md.
2026-09-01 00:41:05 -07:00

11 KiB

RSSPipe Worklog & Architectural Refactoring (009_worklog.md)

This document records the comprehensive architectural overhaul, bug fixes, and engineering enhancements implemented across the RSSPipe frontend and backend.


1. Overview & Objectives

The primary goal of this milestone was to transform RSSPipe into a resilient, reactive, and easily extensible event-driven platform capable of executing asynchronous pipelines (e.g. readability extraction, AI summarization, tagging, text-to-speech) without concurrency bottlenecks or UI state drift.

Core Problems Addressed

  1. Monolithic API Structure: A single 1,140-line handlers.go held all business logic, making feature expansion error-prone.
  2. Missing State Transitions: Internal background updates (e.g., crawler writes, variant additions) lacked declarative event propagation.
  3. Task Concurrency & Recovery: Long-running or rate-limited tasks needed persistent queuing, exponential backoff retries, and crash recovery.
  4. SQLite Concurrency & Lock Collisions: Concurrent worker writes threw SQLITE_BUSY (5) due to pool-level PRAGMA isolation.
  5. Cross-Tab State Synchronization: Updating read/star status in one session did not reflect in other sessions or tabs.
  6. Mobile UX on iOS: Fixed bottom action bars overlapping with the iOS virtual home indicator bar.

2. Architectural Changes

                  ┌───────────────────────────────────────────────────────────┐
                  │                      HTTP API / Worker                    │
                  └─────────────────────────────┬─────────────────────────────┘
                                                │
                                                ▼
                               ┌──────────────────────────────────┐
                               │   EntryService.PatchEntry()      │
                               │  (Atomic DB Update + EntryDiff)  │
                               └────────────────┬─────────────────┘
                                                │
                                                ▼
                               ┌──────────────────────────────────┐
                               │   EventBus.Publish(Transition)   │
                               └────────┬─────────────────┬───────┘
                                        │                 │
                   ┌────────────────────┘                 └────────────────────┐
                   ▼                                                           ▼
┌──────────────────────────────────────┐                   ┌──────────────────────────────────────┐
│       Server-Sent Events (SSE)       │                   │             RulesEngine              │
│       GET /api/events/stream         │                   │    (Evaluate Old vs New State)       │
└──────────────────┬───────────────────┘                   └──────────────────┬───────────────────┘
                   │                                                           │
                   ▼                                                           ▼
┌──────────────────────────────────────┐                   ┌──────────────────────────────────────┐
│  Client Tabs (Real-Time Reactive UI) │                   │       JobEngine.Enqueue(Job)         │
└──────────────────────────────────────┘                   └──────────────────┬───────────────────┘
                                                                               │
                                                                               ▼
                                                           ┌──────────────────────────────────────┐
                                                           │   Persistent SQLite `jobs` Table     │
                                                           │  (Workers, Retries, Idempotency)     │
                                                           └──────────────────────────────────────┘

2.1 Atomic State Transitions & Event Bus

  • Implemented PatchEntry(ctx, id, patch) in internal/services/entry_service.go.
  • Computes atomic EntryDiff comparing pre-update and post-update state:
    • Boolean flag changes (is_read, is_starred).
    • Added or updated variants (e.g. readability_full_text, ai_summary, tts_audio).
  • Publishes EventEntryTransition(OldState, NewState, Diff) onto internal/events/bus.go.
  • Enables declarative rules evaluation on every state change without tight coupling.

2.2 Persistent SQLite Job Queue (JobEngine)

  • Created jobs table in SQLite schema with lifecycle states (pending, running, completed, failed, dead).
  • Added worker pool in internal/worker/engine.go with:
    • Concurrency limits (default 5 concurrent workers).
    • Exponential backoff retries (run_at scheduling).
    • Idempotency key deduplication (prevents duplicate execution of identical tasks).
    • Crash recovery on engine startup (resets zombie running jobs back to pending).
  • Registered handlers for:
    • readability_scrape (Readability full-text extraction).
    • ai_summary / ai_enrich (LLM summarization and variant generation).
    • mark_read / mark_starred (Status modifications).
    • set_variant (Structured tag and variant persistence).

2.3 Modular Backend HTTP Handlers

Decomposed the monolithic handlers.go into domain-specific files under internal/api/:

  • auth_handler.go: Authentication, session verification, login, and logout.
  • feeds_handler.go: Feed CRUD, OPML import/export, manual refresh, and favicon resolution.
  • entries_handler.go: Entry queries, filtering, atomic patch, scrape, and AI actions.
  • jobs_handler.go: Job queue querying, execution status, and asynchronous dispatch.
  • settings_handler.go: User preferences and AI provider configuration.
  • rules_handler.go: Automation rule CRUD and test execution.
  • sse_handler.go: Server-Sent Events real-time event streaming.

2.4 Real-Time Reactive Synchronization (SSE)

  • Added GET /api/events/stream endpoint authenticated via Bearer token / session cookie.
  • Connected event stream in web/src/App.tsx via api.connectEventStream().
  • Automatically syncs read status, star status, and variant additions in real-time across multiple open browser tabs or devices.

3. Critical Bug Fixes

3.1 SQLite SQLITE_BUSY (5) Database Lock Fix

  • Root Cause: In Go's database/sql, executing PRAGMA busy_timeout = 5000; on startup only applied to a single pooled connection. Subsequent pool connections defaulted to 0ms timeout, immediately throwing SQLITE_BUSY when concurrent writes occurred.

  • Solution: Configured PRAGMAs directly in the SQLite DSN connection string in internal/db/db.go:

    dsn := fmt.Sprintf("%s?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=cache_size(-20000)", dbPath)
    

    Every connection checked out of the pool now inherits a 10-second busy timeout and WAL mode.

3.2 AI Settings & Prompt Template Unification

  • Root Cause: SettingsService saved AI configuration as a unified JSON object under key "ai_settings", but LoadConfig in the AI actions was reading non-existent legacy individual keys (ai_enabled, ai_api_key). This caused prompt templates to be ignored and disabled states to be bypassed.
  • Solution: Refactored LoadConfig in internal/actions/ai/enrich.go to unmarshal "ai_settings" JSON. Enforced strict enabled checks and wired custom prompt templates (e.g. prompt "a": "aaa").

3.3 Undo Share State Reset

  • Root Cause: models.FeedEntry had omitempty on ShareUUID *string, which omitted share_uuid from JSON responses when set to nil rather than outputting null.
  • Solution: Removed omitempty from ShareUUID in internal/models/models.go and updated DesktopReader.tsx and MobileArticleSheet.tsx to explicitly reset share_uuid: res.share_uuid ?? null.

3.4 Middle Bar Unread / All Count Discrepancy

  • Root Cause: The middle article list header count displayed in-memory array lengths (entries.length) rather than true database statistics from stats and feed/category metadata.
  • Solution: Updated DesktopArticleList.tsx to compute true database counts from stats and feed totals, and reverted switch buttons to clean Unread and All labels.

3.5 Mobile iOS Safe Area Insets & Home Indicator

  • Root Cause: viewport-fit=cover was missing in web/index.html, causing iOS Safari to evaluate env(safe-area-inset-bottom) to 0px.
  • Solution: Added viewport-fit=cover and calibrated bottom navigation height (50px + env(safe-area-inset-bottom, 0px)) and article bottom toolbar height (48px + env(safe-area-inset-bottom, 0px)).

4. Work Items & Backlog for Future Milestones

The following items have been identified for future implementation:

  1. SSE Auto-Reconnect & Connection State Recovery:

    • Exponential backoff with jitter on EventSource disconnection in web/src/api.ts.
    • Snapshot reconciliation (/api/stats and /api/entries?since=...) upon reconnect.
  2. Background Job Queue & Dead-Letter Management in Admin Settings:

    • Dedicated "Job Queue" tab in Settings > Account & System.
    • Live table of tasks (pending, running, completed, failed, dead) with error logs and manual retry/cancel actions.
  3. Reader UI Prompt Template Selector:

    • Dropdown selector next to "Generate AI Summary" button in Desktop and Mobile reader bars to choose between configured prompt templates on the fly.
  4. Local Ollama Model Autodiscovery & Health Check:

    • "Discover Models" button in Settings > AI & Integrations querying http://localhost:11434/api/tags to populate the model list automatically.
  5. Desktop 3-Pane Middle List Infinite Scrolling:

    • Windowed offset pagination (limit: 50, offset: entries.length) with scroll threshold trigger on .desktop-articles-scroll.