aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.thicket/tickets.jsonl4
-rw-r--r--frontend/src/components/Settings.tsx26
-rw-r--r--frontend/tests/crawl.spec.ts32
3 files changed, 61 insertions, 1 deletions
diff --git a/.thicket/tickets.jsonl b/.thicket/tickets.jsonl
index 09f36e1..86e37ff 100644
--- a/.thicket/tickets.jsonl
+++ b/.thicket/tickets.jsonl
@@ -38,6 +38,7 @@
{"id":"NK-acq08a","title":"update Makefile","description":"Ensure the Makefile builds things and works\nTest it by running it regularly before checking in!","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T00:55:40.127322076Z","updated":"2026-02-14T01:26:31.564799193Z"}
{"id":"NK-ahzf5c","title":"drop \"mark read\" button","description":"there's no mark read/unread buttons, it's just by scrolling!","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T19:28:20.708443259Z","updated":"2026-02-13T20:26:43.029168286Z"}
{"id":"NK-aiaza3","title":"clean up root directory of project","description":"There are some scripts in the root directory like run_e2e.sh that probably should be in a subdirectory -- look into it and make things a little tidier where approopriate.","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T17:42:14.8736959Z","updated":"2026-02-14T23:44:07.081167865Z"}
+{"id":"NK-aibd0t","title":"Add crawl status indicator to UI","description":"Currently triggering a crawl via 'Crawl All Feeds Now' just shows an alert. We should provide a visual status indicator and refresh the item list automatically when done.","type":"task","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-15T05:20:28.975031077Z","updated":"2026-02-15T05:20:28.975031077Z"}
{"id":"NK-arckp3","title":"Install golangci-lint in dev environment","description":"Local Makefile uses 'go vet' because 'golangci-lint' is missing. CI uses golangci-lint. We should install it locally for consistency.","type":"task","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-15T05:14:44.840444844Z","updated":"2026-02-15T05:14:44.840444844Z"}
{"id":"NK-bsdwqz","title":"terminal UI","description":"once there is good test coverage and a clean backend API, work on a nice efficient TUI with https://github.com/charmbracelet/bubbletea","type":"task","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T01:54:02.285738454Z","updated":"2026-02-13T04:42:09.824268427Z"}
{"id":"NK-ca9t70","title":"Vanilla JS: Add Feed UI","description":"Add UI to add a new feed by URL in vanilla JS prototype.","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T04:47:41.764330544Z","updated":"2026-02-14T04:47:41.764330544Z"}
@@ -95,7 +96,7 @@
{"id":"NK-r1aqiw","title":"Implement Subscription List and UserInfo endpoints","description":"","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-15T00:21:51.619650383Z","updated":"2026-02-15T00:44:41.428045944Z"}
{"id":"NK-r39tqq","title":"username + password","description":"it's too weird to have just a password -- in the old UI i had a username but it was just ignored. but password managers get confused by this new no username thing.\n\nlet's make it so you can enter a username and password. to start, just let that be a no-op (it ignores the username and just pays attention to the password.)\n\nwe can consider later on if we want to make the username real and definable too.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-15T02:56:35.68970604Z","updated":"2026-02-15T05:16:49.35160585Z"}
{"id":"NK-r6nhj0","title":"import/export","description":"Import/Export has only ever been partially implemented. Let's finish it up across OPML (de facto standard) but also simple txt line oriented input/output. We may need to file a ticket to deal with the async crawling as part of this.","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T16:45:04.739162003Z","updated":"2026-02-14T17:42:20.713094047Z"}
-{"id":"NK-rhelrq","title":"Add end-to-end integration test for complete crawl cycle","description":"Create an integration test that verifies the complete crawl workflow: start server with background crawling enabled, add a feed via API, wait for background crawl to execute, verify items are fetched and stored. This would require mocking or using a test RSS feed and potentially adjusting timing for faster test execution.","type":"task","status":"open","priority":3,"labels":null,"assignee":"","created":"2026-02-14T20:44:31.052207214Z","updated":"2026-02-14T20:44:31.052207214Z"}
+{"id":"NK-rhelrq","title":"Add end-to-end integration test for complete crawl cycle","description":"Create an integration test that verifies the complete crawl workflow: start server with background crawling enabled, add a feed via API, wait for background crawl to execute, verify items are fetched and stored. This would require mocking or using a test RSS feed and potentially adjusting timing for faster test execution.","type":"task","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-14T20:44:31.052207214Z","updated":"2026-02-15T05:20:23.090352944Z"}
{"id":"NK-ric1zs","title":"Migrate frontend to /api/ endpoints","description":"The backend now provides a clean REST API at /api/. Update the frontend UI to use these new endpoints instead of the legacy backward-compatibility routes (/stream/, /feed/, etc.). This will allow for cleaner separation and better utilization of proper REST patterns.","type":"cleanup","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T04:26:55.864725765Z","updated":"2026-02-13T04:26:55.864725765Z"}
{"id":"NK-rn4nzp","title":"font themes","description":"in the v2 ui, let's offer a few different font stacks the user can switch through. primarily this should just change font-face, maybe size, but don't worry about colors or anything right now.\n\nthe current default (helvetica neue, palatino)\na fancy all serif stack\na no-nonsense modern san-serif stack\na terminal inspired fixed width stack","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T17:10:02.185477382Z","updated":"2026-02-14T18:12:30.253145852Z"}
{"id":"NK-rohuiq","title":"titles changing on read state and hover","description":"Titles are changing on read state from blue to grey. They should just stay blue all the time.\n\nTitles are getting underlined on hover. They should have no underline regardless of hover state.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T03:36:26.36373162Z","updated":"2026-02-14T03:37:50.73870586Z"}
@@ -141,6 +142,7 @@
{"id":"NK-dgbrb79","from_ticket_id":"NK-9hx0y7","to_ticket_id":"NK-t0nmbj","type":"created_from","created":"2026-02-13T05:44:01.556027956Z"}
{"id":"NK-dgfppki","from_ticket_id":"NK-gqkh96","to_ticket_id":"NK-x924bu","type":"created_from","created":"2026-02-13T03:54:30.303602703Z"}
{"id":"NK-dgu0o9d","from_ticket_id":"NK-ca9t70","to_ticket_id":"NK-d4c8jv","type":"created_from","created":"2026-02-14T04:47:41.786634182Z"}
+{"id":"NK-dikc4i2","from_ticket_id":"NK-aibd0t","to_ticket_id":"NK-rhelrq","type":"created_from","created":"2026-02-15T05:20:29.004681893Z"}
{"id":"NK-dikvat5","from_ticket_id":"NK-ck4co9","to_ticket_id":"NK-mpb1e1","type":"created_from","created":"2026-02-15T02:21:35.000829694Z"}
{"id":"NK-dj3r998","from_ticket_id":"NK-thq2oq","to_ticket_id":"NK-9pgjph","type":"created_from","created":"2026-02-14T03:30:58.76860979Z"}
{"id":"NK-dk4vq5o","from_ticket_id":"NK-uy90he","to_ticket_id":"NK-mwf9q2","type":"created_from","created":"2026-02-13T18:05:17.305248863Z"}
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx
index b218775..ec432ba 100644
--- a/frontend/src/components/Settings.tsx
+++ b/frontend/src/components/Settings.tsx
@@ -111,6 +111,25 @@ export default function Settings({ fontTheme, setFontTheme }: SettingsProps) {
});
};
+ const handleCrawl = () => {
+ setLoading(true);
+ apiFetch('/api/crawl', {
+ method: 'POST',
+ })
+ .then((res) => {
+ if (!res.ok) throw new Error('Failed to start crawl');
+ return res.json();
+ })
+ .then(() => {
+ setLoading(false);
+ alert('Crawl started!');
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ };
+
return (
<div className="settings-page variant-glass">
<h2>Settings</h2>
@@ -178,6 +197,13 @@ export default function Settings({ fontTheme, setFontTheme }: SettingsProps) {
<a href="/api/export/json" className="export-btn">JSON</a>
</div>
</div>
+
+ <div className="crawl-section">
+ <h3>Actions</h3>
+ <button onClick={handleCrawl} disabled={loading} className="crawl-btn">
+ Crawl All Feeds Now
+ </button>
+ </div>
</div>
{error && <p className="error-message">{error}</p>}
diff --git a/frontend/tests/crawl.spec.ts b/frontend/tests/crawl.spec.ts
new file mode 100644
index 0000000..175a764
--- /dev/null
+++ b/frontend/tests/crawl.spec.ts
@@ -0,0 +1,32 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Crawl Integration', () => {
+ test('should add a feed and see items after crawl', async ({ page }) => {
+ const mockFeedUrl = 'http://localhost:9090/mock_feed.xml';
+
+ // 1. Login and go to Settings
+ await page.goto('/v2/settings');
+
+ // 2. Add the mock feed
+ await page.fill('input[type="url"]', mockFeedUrl);
+ await page.click('text=Add Feed');
+
+ // Wait for feed to be added
+ await expect(page.getByText(mockFeedUrl)).toBeVisible({ timeout: 5000 });
+
+ // 3. Trigger Crawl
+ const crawlButton = page.getByRole('button', { name: /crawl/i });
+ await expect(crawlButton).toBeVisible();
+
+ // Handle the alert
+ page.on('dialog', dialog => dialog.accept());
+ await crawlButton.click();
+
+ // 4. Go to Home and check for items
+ await page.goto('/v2/');
+
+ // The mock feed has "Mock Item 1" and "Mock Item 2"
+ await expect(page.getByText('Mock Item 1')).toBeVisible({ timeout: 10000 });
+ await expect(page.getByText('Mock Item 2')).toBeVisible({ timeout: 10000 });
+ });
+});