Scaffolded the authoritative documentation structure per INSTRUCTIONS.md v1.1. This is the first documentation pass for the research-digest project.
- Created
docs/INSTRUCTIONS.md(collaboration contract and rules) - Created
docs/PROGRESS.md(this file) - Created
docs/BACKLOG.md(empty structure, ready for items) - Created
docs/TECH_SPEC.md(skeleton with required sections) - Created
docs/OPS.md(placeholder, procedural) - Created
CLAUDE.md(root, AI behavior preferences) - Confirmed project prefix: RDIG
- Initialized git repository
- Added
docs/OPS_PRIVATE.mdto.gitignore
- Project prefix:
RDIG(confirmed by user) - Documentation structure: Following INSTRUCTIONS.md v1.1 standard
- Populate BACKLOG.md with known work items
- Replace boilerplate README.md with project-specific content
Populated TECH_SPEC.md with real architecture derived from reviewing all application code and consolidating prior conversation context (tech spec v0.1 + Abstractly vision notes).
- Reviewed all controllers, services, config files, routes, and views
- Consolidated prior tech spec (v0.1) and Abstractly vision notes into TECH_SPEC.md
- Verified code matches documented architecture — all details confirmed against source
- Documented current sources (16 total: 14 arXiv subfields + bioRxiv + medRxiv)
- Documented all 3 AI providers (Gemini, OpenAI, Ollama) with actual config values
- Captured open questions/risks (session expiry, no caching, missing .env.example vars, etc.)
- Documented planned evolution phases (Stability → Intelligence → Abstractly Vision)
- TECH_SPEC reflects current state of code, not aspirational features
- Planned evolution captured separately at bottom of TECH_SPEC
- The Shelf integration noted as future in External Integrations
- Populate BACKLOG.md with known work items
- Replace boilerplate README.md with project-specific content
- Initialize git and make first commit
Replaced the Laravel boilerplate README with project-specific content for Abstractly.
- Wrote
README.mdwith project overview, features, tech stack, quickstart, and environment variable documentation - Committed as
b6fea33
- README reflects current v0.1 state (single discipline, session-based, no auth)
- Links to BACKLOG.md and TECH_SPEC.md included per INSTRUCTIONS.md requirements
- Populate BACKLOG.md with known work items
Major frontend architecture migration. Replaced traditional MVC controller-based UI with Livewire 3 full-page components. Added comprehensive Dusk E2E test suite. Updated frontend stack to Tailwind CSS v4 with zero-config setup.
- Livewire 3 components created:
DisciplinePicker— replacesDisciplineControllerindex/update flowsSourcePicker— replacesDisciplineControllershow/updateSources flowsDigestViewer— replacesDigestControllergenerate/show flows
- Routes updated to mount Livewire components directly as full-page endpoints
- Companion Blade views created in
resources/views/livewire/with modern Tailwind UI:- Interactive card grids with toggle, select all/none, save
- Breadcrumb navigation, loading states, color-coded AI perspectives
wire:navigatefor SPA-like transitions
- Layout updated (
components/layouts/app.blade.php) with sticky nav, responsive container, footer - Dusk E2E tests added (37 test cases across 7 files) covering full user workflow
- Legacy controllers retained (
DigestController,DisciplineController) but no longer routed - SourceController remains the only active traditional controller (preview endpoint)
- Frontend dependencies updated: Tailwind v4 (
@tailwindcss/vite), Vite 7,livewire/livewire^3.0
- Livewire components replace controllers for all interactive UI flows
- Legacy controllers kept in codebase (not deleted) — may serve as reference or future API endpoints
- Source preview remains a traditional controller endpoint (no interactivity needed)
- Tailwind v4 zero-config approach: no
tailwind.config.js, all config in CSS via@theme - Dusk tests use
chrome-headless-shell-mac-arm64for native ARM64 support
- Sync documentation to reflect Livewire migration (TECH_SPEC was out of date)
- Populate BACKLOG.md with known work items
- Decide whether to remove legacy controllers or keep for API use
Full codebase audit and documentation sync to bring TECH_SPEC.md and PROGRESS.md in line with the Livewire 3 migration (commit 99b7e89) and README replacement (commit b6fea33) that were not previously logged.
- Audited entire codebase: Livewire components, controllers, services, config, routes, views, tests, frontend tooling
- Updated TECH_SPEC.md:
- Feature/View Breakdown: all sections updated from controller references to Livewire component references
- Source Preview: clarified as only active traditional controller endpoint
- Digest Generation & Display: merged into one section reflecting Livewire reactive pattern
- Architecture Overview: rewritten with Livewire-first diagram, legacy controller annotations, routing table, updated frontend stack
- Testing Strategy: expanded from 4 lines to full PHPUnit + Dusk breakdown (37 E2E test cases)
- Fixed source count: arXiv subfields 13 → 14, total sources 16 → 17
- Updated PROGRESS.md: Added entries for README replacement, Livewire migration, and this sync
- TECH_SPEC architecture diagram no longer references controller-based UI flows
- arXiv subfield count corrected (listed 14 codes but labeled as 13)
- Total source count corrected to 17
- PROGRESS.md now reflects all committed work to date
- Populate BACKLOG.md with proposed work items (derived from audit findings, TECH_SPEC open questions, and planned evolution phases)
Expanded from 1 active discipline (math) to 13 active disciplines with 49 total source entries. Added two new parser types (OSF Preprints, Europe PMC) to SourcePreviewer. Added Http::fake() canned responses for all source types in the Dusk test environment. Renamed branding from "Research Digest" to "Abstractly" in the layout.
- 30 new sources added to
config/sources.phpacross 12 disciplines:- CS & AI: 8 sources (arXiv cs.* categories)
- Earth & Environmental: 4 sources (arXiv physics + astro-ph + Europe PMC)
- Economics & Finance: 4 sources (arXiv econ + q-fin)
- Informatics: 3 sources (arXiv q-bio: QM, GN, MN)
- Engineering: 3 sources (arXiv eess + cs.RO)
- Neuroscience: 2 sources (arXiv q-bio.NC + Europe PMC)
- Pharmacology: 2 sources (arXiv q-bio.BM + Europe PMC)
- Agriculture: 2 sources (arXiv q-bio.PE + Europe PMC)
- Psychology: 1 source (PsyArXiv via OSF) + shared q-bio.NC
- Linguistics: 1 source (arXiv cs.CL)
- Education: 1 source (EdArXiv via OSF)
- Communication: 1 source (SocArXiv via OSF)
- 13 of 15 disciplines set to
ready=true(only Law and Arts remain disabled) - New parser methods in
SourcePreviewer:fetchOsfPreprints()— handles OSF Preprints JSON:API (PsyArXiv, SocArXiv, EdArXiv)fetchEuropePmc()— handles Europe PMC REST JSON
- Http::fake() canned responses added to
AppServiceProviderfor Dusk environment (OSF, Europe PMC, in addition to existing arXiv/bioRxiv/medRxiv/Gemini stubs) - Branding committed: layout title and footer changed from "Research Digest" to "Abstractly"
- Dusk test assertions updated for branding change
- Committed as
ef07bee
- Cross-listing:
arxiv_qbio_NC(Neurons and Cognition) mapped to both neuroscience and psychology disciplines - Law and Arts remain
ready=false— no obvious open-access preprint sources identified yet - OSF Preprints API used for psychology, education, and communication (PsyArXiv, EdArXiv, SocArXiv)
- Europe PMC used as aggregated preprint source for neuroscience, earth, pharmacology, and agriculture
- Re-sync documentation to reflect this expansion (TECH_SPEC source counts, external integrations, parser docs are stale)
- Mark RDIG-007 as Done in BACKLOG.md
Second documentation sync in this session. Updated TECH_SPEC.md, BACKLOG.md, and PROGRESS.md to reflect the multi-discipline source expansion (commit ef07bee).
- Updated TECH_SPEC.md:
- Discipline readiness: "only math is ready" → "13 of 15 are ready"
- Source table: replaced 4-row math-only table with full 13-discipline breakdown (49 sources)
- Source Fetching: added
fetchOsfPreprints()andfetchEuropePmc()to parser table - External Integrations: added OSF Preprints API and Europe PMC REST API
- Testing Strategy: added note about Http::fake() Dusk test isolation
- Updated BACKLOG.md:
- RDIG-007: moved from Medium to Done with updated description reflecting actual scope (12 disciplines, not 1)
- RDIG-002: updated description to reflect 49 sources across 5 provider types
- RDIG-005: updated acceptance criteria to cover all 5 parser types
- Updated PROGRESS.md: Added entry for commit
ef07beeand this sync
- Work on backlog items (RDIG-001 through RDIG-006 remain open)
- Law and Arts disciplines still need sources before they can be enabled
Architectural simplification: removed database dependency entirely. The app is a local-only, single-user research tool — file-based sessions and cache are sufficient. Added digest JSON export feature (local file + browser download). Completed RDIG-001 (AI env vars) and created/completed RDIG-010 (export). Archived RDIG-006 (DB persistence — no longer applicable).
- Removed database dependency:
.env.example: SESSION_DRIVER →file, CACHE_STORE →file, QUEUE_CONNECTION →sync, DB_CONNECTION commented outcomposer.json: removedphp artisan migrate --forcefrom setup script- APP_NAME updated to
Abstractlyin.env.example
- Added AI env vars to
.env.example(RDIG-001):GOOGLE_API_KEY,DIGEST_AI_PROVIDER,DIGEST_AI_MODEL,OPENAI_API_KEY,DIGEST_AI_MODEL_OPENAI,OLLAMA_HOSTwith descriptive comments - Added digest export (RDIG-010):
DigestViewer::export()method saves JSON tostorage/app/digests/and triggers browser download- "Export JSON" button in digest-viewer blade (visible only when digest has content)
- Created
storage/app/digests/.gitkeepdirectory
- Updated documentation:
- TECH_SPEC: Data Model section rewritten for no-database architecture + export feature
- BACKLOG: RDIG-001 moved to Done, RDIG-006 archived, RDIG-010 added as Done
- PROGRESS: this entry
- No database at all — file-based sessions, cache, and sync queue are sufficient for local single-user use
- Export over persistence — digests are ephemeral by default; user exports interesting ones on demand as JSON
- RDIG-006 archived — DB persistence is not needed; if selection persistence becomes needed, a JSON config file approach would be more appropriate
- RDIG-003 (queue jobs) deprioritized — sync queue is fine for single-user local; keeping it in backlog but less urgent
- Remaining backlog: RDIG-002 (caching), RDIG-003 (queue jobs), RDIG-004 (legacy controllers), RDIG-005 (unit tests), RDIG-008 (dedup), RDIG-009 (wire:navigate)
- Update local
.envto match new.env.exampledrivers - Law and Arts disciplines still need sources
Centralized AI provider configuration into config/ai.php, added comprehensive unit tests for both core services (21 test cases total), cleaned up dead code in SourcePreviewer, and updated local .env to match the new no-database .env.example. Documentation sync triggered by reaching 5 completed backlog items.
config/ai.phpcreated: Centralizes all AI provider env vars (DIGEST_AI_PROVIDER,GOOGLE_API_KEY,DIGEST_AI_MODEL,OPENAI_API_KEY,DIGEST_AI_MODEL_OPENAI,OLLAMA_HOST,DIGEST_AI_MODEL_OLLAMA) into proper Laravel configAiSummarizerrefactored: Allenv()calls replaced withconfig('ai.*')calls for consistency with Laravel conventionsSourcePreviewercleaned: Removed deadhal_mathcase from the match statement- Unit tests added (RDIG-005):
SourcePreviewerTest— 12 test cases covering all 5 parser types, limit, empty feed, HTTP errorsAiSummarizerTest— 9 test cases covering all 3 providers, missing keys, failures, batching, fallback, placeholder
.envupdated:APP_NAME=Abstractly, DB_CONNECTION/DB_DATABASE commented out- Documentation sync:
- TECH_SPEC:
config/ai.phpadded to architecture diagram, AI config reference updated, unit test coverage table added - BACKLOG: RDIG-005 moved to Done
- PROGRESS: this entry
- TECH_SPEC:
- Committed as
90bce15(code + tests), documentation committed separately
- All AI config centralized in
config/ai.php—AiSummarizerno longer callsenv()directly APP_ENV=duskleft as-is in.env(activates Http::fake() stubs in AppServiceProvider — user aware)- Dead
hal_mathsource case removed rather than left as commented-out code
- Remaining backlog: RDIG-002 (caching), RDIG-003 (queue jobs), RDIG-004 (legacy controllers), RDIG-008 (dedup), RDIG-009 (wire:navigate)
- Law and Arts disciplines still need sources
Two maintenance backlog items completed as a batch. Deleted dead legacy controllers and standardized wire:navigate across all internal links for consistent SPA-like navigation.
- RDIG-004 — Deleted legacy controllers:
- Removed
app/Http/Controllers/DigestController.php(80 lines) - Removed
app/Http/Controllers/DisciplineController.php(105 lines) - Verified zero PHP references remain (no routes, imports, or tests)
- Only
Controller.php(base class) andSourceController.php(preview endpoint) remain
- Removed
- RDIG-009 — Added
wire:navigateto 8 internal links:components/layouts/app.blade.php: logo link, Disciplines nav link, Digest nav link (3 links)livewire/discipline-picker.blade.php: "View digest" button (1 link)sources/preview.blade.php: "Disciplines" breadcrumb, discipline label breadcrumb, "Back to {label}" button (3 links)livewire/source-picker.blade.php: "Preview" button (1 link — navigates to traditional controller but benefits from smoother transition)
- Documentation updated: BACKLOG.md (both items moved to Done), PROGRESS.md (this entry)
- Delete over relocate for legacy controllers — no API endpoints are planned, and the Livewire component logic has diverged enough that the old controllers wouldn't be a useful starting point
- Include preview link in
wire:navigatestandardization — even though it targets a traditional controller endpoint,wire:navigatestill provides a smoother transition experience with no downside
- Remaining backlog: RDIG-002 (caching, in progress separately), RDIG-003 (queue jobs), RDIG-008 (dedup)
- Law and Arts disciplines still need sources
Added a cache layer to SourcePreviewer::fetch() with configurable TTL. Repeated digest generations within the TTL window now return cached results without issuing HTTP requests. Cache bypass is available via UI checkbox, query parameter, and artisan command.
config/sources.php: Added'cache_ttl' => env('SOURCE_CACHE_TTL', 3600)config key.env.example: AddedSOURCE_CACHE_TTL=3600with descriptive comment blockapp/Services/SourcePreviewer.php: Cache read/write wrapping thematchdispatch infetch(). Cache key issource_preview:+ md5 of URL + limit. TTL=0 disables caching entirely.$forceRefreshparameter forgets key before fetching. Exceptions bubble up uncached.app/Livewire/DigestViewer.php: Addedpublic bool $forceRefresh = falseproperty, passed through tofetch()app/Http/Controllers/SourceController.php: Reads?fresh=1query param, passes as$forceRefreshtofetch()app/Console/Commands/ClearSourceCache.php: New artisan commandsource:clear-cachethat callsCache::flush()resources/views/livewire/digest-viewer.blade.php: Added "Skip cache" checkbox bound towire:model="forceRefresh"next to the Generate buttontests/Unit/SourcePreviewerTest.php: 5 new test cases — cache on first call, cached return without HTTP, force refresh bypass, different limits = different keys, TTL=0 disables caching- Documentation: BACKLOG.md (RDIG-002 moved to Done), PROGRESS.md (this entry)
- Committed as
0aeab73
- Cache inside
fetch()rather than at the caller level — all callers (DigestViewer, SourceController) benefit automatically Cache::flush()in artisan command — safe because nothing else uses the cache store in this app- TTL=0 as kill switch — allows disabling caching entirely without code changes
- Exceptions not cached — transient failures should be retried on the next call, not served from cache
- Remaining backlog: RDIG-003 (queue jobs), RDIG-008 (dedup)
- Law and Arts disciplines still need sources
Revised RDIG-003 from "move digest generation to a queued job" to "stream digest generation with progressive UI using Livewire 3's $this->stream()." A full queue stack (driver, workers, polling, failure handling) is unnecessary overhead for a local single-user app. Livewire streaming achieves the same UX goal — progressive results instead of a blocked spinner — without infrastructure changes.
- BACKLOG.md: Rewrote RDIG-003 title, description, and acceptance criteria to reflect the streaming approach. Added context on why the queue approach was dropped and how RDIG-002 (caching) complements this change.
- PROGRESS.md: This entry.
- Streaming over queuing —
$this->stream()pushes partial HTML to the browser as each source completes. No queue driver, workers, or polling needed. - Queue approach archived — original description preserved in git history (pre-edit state) but the backlog now reflects the revised plan
- Caching as complement — RDIG-002's cache layer means only cold first-run generations are slow; streaming addresses the UX of those cold runs specifically
- Implement RDIG-003 (Livewire streaming)
- Remaining backlog: RDIG-008 (dedup)
- Law and Arts disciplines still need sources
Added per-discipline paper deduplication between the fetch and summarize stages of digest generation. Cross-listed papers (e.g., appearing in both math.PR and math.ST) now appear only once — under the first source that returned them — with small gray pill badges indicating other sources that also had the paper. This saves AI summarization budget and reduces digest clutter.
- New file:
app/Services/PaperDeduplicator.php— Stateless service with two public methods:dedup(array $fetchedBySource): array— Removes duplicates from later sources, annotates first occurrences withalso_in(list of other source labels). Fast-paths for 0–1 sources. Items with non-dedupable URLs (#, empty) always kept.normalizeUrl(string $url): ?string— Lowercases, upgrades http→https, strips trailing slash, strips arXiv version suffix (v1,v3, etc.). Returns null for#, empty, whitespace. Does not affect DOI or other non-arXiv URLs.
- New file:
tests/Unit/PaperDeduplicatorTest.php— 25 test cases across 5 groups:normalizeUrlnull returns (3):#, empty, whitespacenormalizeUrltransformations (7): lowercase, http→https, trailing slash, arXiv version strip (v1, vN), non-arXiv preservation, DOI preservationdedupfast paths (2): empty input, single sourcededupcore behavior (5): single dupe removed, also_in annotation, multi-source dupe, mixed unique/dupe, no also_in on unique itemsdedupedge cases (8):#URLs kept, http/https normalization, arXiv version normalization, fully-emptied source, DOI dedup, empty URL items, source order preservation, also_in survives+merge operator
- Modified:
app/Livewire/DigestViewer.php— Refactored inner loop from sequential fetch+summarize to three-pass:- Fetch all sources for the discipline into
$fetchedBySource - Run
PaperDeduplicator::dedup()to remove cross-source duplicates - Summarize only unique items per source (sources emptied by dedup are skipped)
- Fetch all sources for the discipline into
- Modified:
resources/views/livewire/partials/digest-section.blade.php— Addedalso_inbadge display between the paper title link and the summary paragraph. Small gray pill badges with metadata styling. - Updated documentation: BACKLOG.md (RDIG-008 moved to Done), PROGRESS.md (this entry)
- Dedup per-discipline only — Cross-discipline duplication (e.g.,
arxiv_qbio_NCin both Neuroscience and Psychology) is intentional and left alone - First occurrence wins — The paper stays in whichever source appears first in the config ordering; later sources lose their copy
also_inis additive — The key survives throughAiSummarizer::mergeBatchSummaries()because PHP's+operator gives left-side precedence, so existing keys on the item are preserved- Stateless service —
PaperDeduplicatorhas no dependencies; injected via Livewire method injection likeSourcePreviewerandAiSummarizer
- Implement RDIG-003 (Livewire streaming)
- Law and Arts disciplines still need sources
Replaced the static loading spinner with Livewire 3 streaming. Digest sections now render progressively as each discipline completes, and a real-time status bar shows which source is being fetched or summarized. No queue infrastructure — the generation remains synchronous on the server, but the UI updates incrementally via $this->stream().
- New file:
resources/views/livewire/partials/digest-section.blade.php— Extracted discipline section markup into a reusable partial. Used for both streaming (rendered to HTML viaview()->render()) and the final Livewire re-render (via@include). - Modified:
resources/views/livewire/digest-viewer.blade.php— Replaced the static loading overlay with twowire:loadingareas:- Progress status bar with
wire:stream="progress-status"(replaces text in-place) - Streamed results container with
wire:stream="digest-stream"(appends discipline sections) - Final content area (
wire:loading.remove) uses@includeof the partial for the post-action render
- Progress status bar with
- Modified:
app/Livewire/DigestViewer.php— Added$this->stream()calls ingenerate():- Before each source fetch:
"Fetching {source label}…"→progress-status(replace) - Before each AI summarization:
"Summarizing {source label}…"→progress-status(replace) - After each discipline completes: rendered section HTML →
digest-stream(append)
- Before each source fetch:
- Documentation: BACKLOG.md (RDIG-003 moved to Done), PROGRESS.md (this entry)
- Committed as
456aa49
- Streaming over queuing —
$this->stream()achieves the same progressive UX without queue drivers, workers, or polling. Appropriate for a local single-user app. wire:loadingstructure preserved — During the action,wire:loadingdivs show the progress bar + streamed results. After the action,wire:loading.removeshows the final rendered digest. Content is identical, so the swap is seamless.- Blade partial extraction — Single source of truth for discipline section markup, avoiding duplication between streamed HTML and the final Livewire render.
- All formal backlog items are complete
- Law and Arts disciplines still need sources
- Parking lot ideas available for future work
Batch implementation of 8 gap-analysis items identified from a tech spec audit. Covers error visibility, AI caching, Dusk E2E coverage, rate limiting, export metadata, session lifetime UX, config validation, and enabling the final two disciplines. All 15 disciplines are now active with 51 total sources.
-
RDIG-011 (Critical) — Surface failed sources:
- Added
$failuresarray toDigestViewer, populated in catch blocks with source label and error type (fetchorsummarize) - Added amber warning banner to
digest-viewer.blade.phplisting failed sources after generation
- Added
-
RDIG-012 (High) — Cache AI summaries:
- Added cache layer to
AiSummarizer::summarizeItems()keyed onai_summary:+ md5(lowercase URL) - Extracted
callProvider()andsummaryCacheKey()helpers - Configurable via
AI_SUMMARY_CACHE_TTLenv var (default 24h, 0 disables) forceRefreshflag passes through from DigestViewer to bypass both source and summary caches- 5 new unit tests for cache behavior
- Added cache layer to
-
RDIG-013 (Medium) — Dusk tests for shipped features:
- Created
tests/Browser/DigestFeaturesTest.phpwith 9 test cases - Covers: export button visibility, skip cache checkbox, streaming elements, also_in badges, session lifetime warning, failure banner
- Created
-
RDIG-014 (Medium) — Rate-limit API calls:
- Added
AI_BATCH_DELAY_MSconfig (default 200ms) withusleep()between AI batches - All 3 providers now use
->retry(2, ...)with conditional 429 backoff andthrow: false - Added
batchDelay()helper to AiSummarizer
- Added
-
RDIG-015 (Medium) — Export metadata envelope:
- Wrapped digest JSON export in
{ meta: { generated_at, format_version, disciplines, sources }, digest: [...] } - Also writes to
storage/app/digests/for local archival
- Wrapped digest JSON export in
-
RDIG-016 (Medium) — Session lifetime warning:
- Added help text below save buttons on both discipline-picker and source-picker views
- Text reads: "Selections are stored in your browser session and persist for N minutes of inactivity"
-
RDIG-017 (Low) — Config validation:
- Created
tests/Unit/ConfigIntegrityTest.phpwith 7 test cases - Validates: unique keys, required fields, non-empty URLs, discipline references, labels, ready flags
- Created
-
RDIG-018 (Low) — Law and Arts sources:
- Added LawArXiv (
lawarxiv_recent) for Law, MediArXiv (mediarxiv_recent) for Arts (both OSF Preprints) - Set both disciplines to
ready=true— all 15 disciplines now active - Existing
api.osf.io/*Http::fake() stub covers new sources
- Added LawArXiv (
- 64 tests, 538 assertions — all passing
- 9 new Dusk test cases (DigestFeaturesTest)
- 7 new unit tests (ConfigIntegrityTest)
- 5 new unit tests (AiSummarizerTest cache behavior)
- Batch implementation — User explicitly requested all 8 items be worked on together rather than one-at-a-time
- 429 retry with
throw: false— Necessary to preserve existing placeholder-on-failure logic in AiSummarizer - LawArXiv and MediArXiv selected as sources for Law and Arts respectively — both are OSF Preprints servers, reusing the existing
fetchOsfPreprintsparser - No new Http::fake() stubs needed — existing
api.osf.io/*wildcard covers all OSF-based sources
- Commit all changes
- Documentation sync (triggered by 5+ backlog items completed)
- Parking lot ideas available for future work
Added a "Saved Papers" feature that lets users bookmark individual papers from the digest for later reference. Papers persist in a local JSON file (storage/app/saved-papers.json), surviving session expiry. Includes a dedicated /saved page with viewing, removing, and exporting capabilities, plus a nav link with count badge.
-
New file:
app/Services/SavedPapersRepository.php— Stateless read/write service with in-memory cache per request. Public methods:all(),save(),remove(),has(),savedUrls(),count(),clear(),export(). Constructor accepts optional$pathfor testability. File format:{ format_version: 1, papers: [...] }. -
New file:
tests/Unit/SavedPapersRepositoryTest.php— 20 test cases across 7 groups: empty state (4), save (7 — persist, saved_at, field preservation, multiple, ordering, dedup, timestamp update), remove (3), clear (1), savedUrls (1), export (2), file format (2 — format_version, corruption recovery). -
Modified:
app/Livewire/DigestViewer.php— Added$savedUrlspublic array property, populated inmount()via repository injection. AddedtoggleSave(string $url)action that toggles save/remove and refreshes$savedUrls. Added privatefindPaperByUrl()helper that walks the nesteddigest[].sections[].items[]structure and extracts the paper with discipline/source context. Streaming render now passes$savedUrlsto the partial. -
Modified:
resources/views/livewire/partials/digest-section.blade.php— Paper title wrapped in flex container with bookmark toggle button. When$savedUrlsis defined (Livewire render): functional toggle withwire:click="toggleSave(...)", filled amber bookmark icon when saved, outline gray when not. When$savedUrlsis not defined (streaming render): inert gray placeholder icon, nowire:click. -
Modified:
resources/views/livewire/digest-viewer.blade.php—@includenow passes$savedUrlsto the digest-section partial. -
New file:
app/Livewire/SavedPapers.php— Full-page Livewire component at/saved. Methods:mount()(loads papers),removePaper()(single remove),clearAll()(withwire:confirm),export()(JSON streamDownload). -
New file:
resources/views/livewire/saved-papers.blade.php— Breadcrumb navigation, header with count and Export/Clear All buttons, empty state with bookmark icon and link to digest, paper cards with title link, discipline badge (indigo), source badge (gray), relative timestamp, also_in badges, ELI5/SWE/Investor sections (same color-coded borders as digest), X remove button, and footer persistence note. -
Modified:
routes/web.php— AddedRoute::get('/saved', SavedPapers::class)->name('saved.index'). -
Modified:
resources/views/components/layouts/app.blade.php— Added@inject('savedPapers', ...)and "Saved" nav link after "Digest" with amber count badge whencount > 0. -
Modified:
tests/Browser/DigestFeaturesTest.php— Added 3 new Dusk test cases: bookmark button present on generated digest, bookmark toggle saves paper (verifies on/savedpage), empty state on/savedpage. AddedtearDown()to clean upsaved-papers.json. -
Updated documentation: BACKLOG.md (RDIG-019 added to Done, removed from Parking Lot), PROGRESS.md (this entry).
- 84 unit/feature tests, 587 assertions — all passing
- 20 new unit tests (SavedPapersRepositoryTest)
- 3 new Dusk test cases (DigestFeaturesTest)
- JSON file over database — Consistent with the project's no-database architecture. File at
storage/app/saved-papers.jsonis already git-ignored by the existing wildcard. - Stateless repository with in-memory cache — Avoids redundant file reads within the same request. Fresh instance per request ensures consistency.
- Dedup by URL on save — Re-saving the same paper updates
saved_attimestamp rather than creating duplicates. - Streaming render uses inert placeholder — During
$this->stream(),$savedUrlsis not available in the Blade context, so an inert gray bookmark icon is shown. After the action completes and Livewire re-renders, the functional toggle replaces it. @injectin layout — Provides the saved papers count to the nav badge without modifying every Livewire component that uses the layout.
- Commit all changes
- Parking lot ideas available for future work
Added Docker support for zero-friction local development, addressing GitHub issue #3. Three-stage multi-stage Dockerfile handles composer deps, Vite/Tailwind build (with vendor for @source scanning), and a php:8.4-cli runtime. Named volume persists saved papers, cache, and sessions across restarts.
- New file:
.dockerignore— Excludesvendor/,node_modules/,.git/,.env, IDE dirs, test artifacts,public/build/,bootstrap/cache/*.php, and docs from Docker build context - New file:
docker-entrypoint.sh— Runtime init script: copies.env.example→.envif missing, generatesAPP_KEYviakey:generate --show+sed, caches config/routes/views, thenexec "$@" - New file:
Dockerfile— Three-stage build:- Stage 1:
composer:2with--no-dev --ignore-platform-req=php+(composer image ships PHP 8.5, lockfile deps cap at 8.4) - Stage 2:
node:20-alpinerunsnpm ci+npm run buildwith vendor copied from stage 1 (Tailwind v4@sourceneeds vendor blade files) - Stage 3:
php:8.4-cliwith bcmath, opcache, pcntl, zip extensions;php artisan package:discoverregenerates manifest for production-only deps;artisan serveas CMD
- Stage 1:
- New file:
docker-compose.yml— Singleappservice, port 8000,env_file: .env, named volumeapp-storageforstorage/,restart: unless-stopped - Modified:
README.md— Added "Docker (recommended)" subsection to Quickstart with 4-line setup instructions; existing setup moved under "Local" subheading - Updated documentation: BACKLOG.md (RDIG-020 added to Done), TECH_SPEC.md (new Docker / Deployment section), PROGRESS.md (this entry)
- Committed as
08754f0
composer:2ships PHP 8.5 —nette/schema v1.3.2in lockfile requires PHP 8.1–8.4. Fixed with--ignore-platform-req=php+since the final runtime isphp:8.4-clibootstrap/cache/packages.phpfrom local dev — Referenced dev-only service providers (Dusk, Pail, Sail, Collision) causing crash loop. Fixed by addingbootstrap/cache/*.phpto.dockerignoreand runningphp artisan package:discoverin the Dockerfilekey:generate --forcefailed silently — Warning "No APP_KEY variable was found in the .env file" despiteAPP_KEY=being present. Replaced withkey:generate --show+sedfor reliable key writing
- Three stages, not two — Tailwind v4
@sourcedirectives scan vendor blade files, requiring composer output in the node build stage php:8.4-clibase — No nginx/apache needed;artisan serveis sufficient for single-user local tool--no-devon composer — Dusk, Pail, PHPUnit not needed in runtime image- APP_KEY generated at container start — Not baked into image layer; uses
--show+sedinstead of--forcefor reliability - Named volume for
storage/— Saved papers, AI summary cache, and sessions persist across container restarts and rebuilds
- Parking lot ideas available for future work
Fixed a bug where all arXiv source fetches failed due to an HTTP-to-HTTPS redirect, and moved navigation links to the right side of the top bar for better visual hierarchy.
-
Bug fix — arXiv 301 redirect:
- All arXiv URLs in
config/sources.phpusedhttp://export.arxiv.org/...but arXiv now enforces HTTPS, returning a 301 redirect - Laravel's HTTP client does not follow redirects by default, so
!$res->ok()treated the 301 as a failure, throwingRuntimeException - Fix: Updated all 40 arXiv URLs from
http://tohttps://inconfig/sources.php - Additional: Bumped
SourcePreviewerHTTP client timeout from 20s to 45s — arXiv API response times observed at 30+ seconds under normal load - Updated TECH_SPEC.md to reflect new timeout value
- All arXiv URLs in
-
UX — Navigation layout:
- Moved Disciplines, Digest, and Saved links from the left side (grouped with logo) to the right side of the top navigation bar
- Logo remains on the left; existing
justify-betweenflex container handles the spacing - Modified
resources/views/components/layouts/app.blade.php
| File | Change |
|---|---|
config/sources.php |
All arXiv URLs: http:// → https:// |
app/Services/SourcePreviewer.php |
Timeout: 20s → 45s |
resources/views/components/layouts/app.blade.php |
Nav links moved to right side |
docs/TECH_SPEC.md |
HTTP client timeout updated to 45s |
- URL fix over redirect-following — Updating the URLs is cleaner than adding redirect-following behavior to the HTTP client. The root cause was stale URLs, not a missing feature.
- 45s timeout — arXiv observed at 30+ seconds for single-result queries. 45s gives headroom without being excessively long. The per-request
set_time_limit(120)in DigestViewer still governs the overall generation timeout.
- Parking lot ideas available for future work