aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-14 09:38:31 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-14 09:38:31 -0800
commit23947045c011e84149bc1b9d48805e57bb0bb3ba (patch)
treea4eb5015a5456a0ac93a792f59270675beccabd5
parent22eacbaf2712aee2a1c448a0e53f241f8c7bd255 (diff)
downloadneko-23947045c011e84149bc1b9d48805e57bb0bb3ba.tar.gz
neko-23947045c011e84149bc1b9d48805e57bb0bb3ba.tar.bz2
neko-23947045c011e84149bc1b9d48805e57bb0bb3ba.zip
routing: make new UI default at / and move legacy UI to /v1/ (fixing NK-mgmn5m, NK-p89hyt)
-rw-r--r--.thicket/tickets.jsonl6
-rw-r--r--frontend/src/App.tsx4
-rw-r--r--web/routing_test.go75
-rw-r--r--web/web.go8
4 files changed, 89 insertions, 4 deletions
diff --git a/.thicket/tickets.jsonl b/.thicket/tickets.jsonl
index db0ad3d..4851eb2 100644
--- a/.thicket/tickets.jsonl
+++ b/.thicket/tickets.jsonl
@@ -25,6 +25,7 @@
{"id":"NK-9hx0y7","title":"Implement Frontend Login","description":"Create login page and auth logic in the new React frontend. Port functionality from legacy login.html.","type":"","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T05:44:01.546395342Z","updated":"2026-02-13T05:50:33.877452063Z"}
{"id":"NK-9pgjph","title":"v2 ui - font size 18px","description":"Compare your font sizes with the legacy version -- I think they're a little too small (16 vs 18 baseline)","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T03:21:48.453217898Z","updated":"2026-02-14T03:24:25.316927886Z"}
{"id":"NK-a217qm","title":"font styles","description":"Switch the default font stack and size to match the legacy UI","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T00:59:37.686539676Z","updated":"2026-02-14T01:25:03.119825567Z"}
+{"id":"NK-a7c6lb","title":"coverage status","description":"check coverage status -- are we still close to 80%\nit's ok to ignore the old static legacy javascript or vanilla js prototype\nif it's low file a ticket to get coverage back up","type":"task","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-14T17:32:19.995215347Z","updated":"2026-02-14T17:32:19.995215347Z"}
{"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-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"}
@@ -56,13 +57,13 @@
{"id":"NK-lrew5z","title":"UI Styling: Global Typography \u0026 Layout (Fixed Width)","description":"","type":"","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-13T18:04:57.254341682Z","updated":"2026-02-13T18:11:31.436752093Z"}
{"id":"NK-m8bya7","title":"Fix and Re-enable Playwright E2E Tests","description":"E2E tests were crashing the VM and timing out. Disabled them in package.json. Need to investigate resource usage and re-enable.","type":"bug","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T03:08:18.501189975Z","updated":"2026-02-14T04:00:03.995357386Z"}
{"id":"NK-mbuw7q","title":"v2 ui bug - panel open by default","description":"Panel is closed by default, it should be open by default on desktop.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T02:46:56.870671639Z","updated":"2026-02-14T03:08:17.322841854Z"}
-{"id":"NK-mgmn5m","title":"serve \"legacy\" version UI at /v1/ instead of /","description":"Let's \"softly\" start to deprecated the legacy version by moving it to /v1/ -- ideally this won't require any changes but there may be some relative/absolute URLs to adjust in the static files there or in rouoting","type":"task","status":"open","priority":0,"labels":null,"assignee":"","created":"2026-02-14T16:41:04.710679944Z","updated":"2026-02-14T16:41:04.710679944Z"}
+{"id":"NK-mgmn5m","title":"serve \"legacy\" version UI at /v1/ instead of /","description":"Let's \"softly\" start to deprecated the legacy version by moving it to /v1/ -- ideally this won't require any changes but there may be some relative/absolute URLs to adjust in the static files there or in rouoting","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T16:41:04.710679944Z","updated":"2026-02-14T17:38:25.35292336Z"}
{"id":"NK-mwf9q2","title":"Implement Tag View","description":"Create frontend view for browsing items by tag/category. Use /tag/:id endpoint.","type":"","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-13T15:04:12.441165286Z","updated":"2026-02-13T18:04:38.644796168Z"}
{"id":"NK-n7nuyy","title":"Fix TypeScript Lint Errors in Tests","description":"There are lint errors in test files regarding jest-dom matchers (toBeInTheDocument, etc). Ensure proper types are included.","type":"bug","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T21:50:15.140702806Z","updated":"2026-02-13T21:50:15.140702806Z"}
{"id":"NK-o3n9jf","title":"[security] Run Docker Container as Non-Root User","description":"Update the Dockerfile to create and use a non-privileged user. 1. Create a user (e.g., neko) in the final stage. 2. Ensure the /app/data directory is owned by this user. 3. Switch to this user using USER neko before the CMD.","type":"","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T16:35:58.328232962Z","updated":"2026-02-14T17:18:34.748238191Z"}
{"id":"NK-ojdcmq","title":"UI: Add skeleton loaders for feed item loading","description":"The currently 'Loading more...' text is basic. We should add skeleton loaders for a smoother infinite scroll experience.","type":"task","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T19:45:07.376295295Z","updated":"2026-02-13T19:45:07.376295295Z"}
{"id":"NK-op5594","title":"Ensure 80% Frontend Test Coverage","description":"Configure coverage reporting in vitest and ensure the frontend codebase maintains at least 80% test coverage.","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-13T05:46:24.13314466Z","updated":"2026-02-13T05:50:46.728239299Z"}
-{"id":"NK-p89hyt","title":"make new v2 UI the default and serve at /","description":"After we move the old UI to be served at v1, serve the new UI at /\n\nWe can keep serving it at v2/ as well if we want.","type":"task","status":"open","priority":0,"labels":null,"assignee":"","created":"2026-02-14T16:42:20.13241547Z","updated":"2026-02-14T16:42:20.13241547Z"}
+{"id":"NK-p89hyt","title":"make new v2 UI the default and serve at /","description":"After we move the old UI to be served at v1, serve the new UI at /\n\nWe can keep serving it at v2/ as well if we want.","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T16:42:20.13241547Z","updated":"2026-02-14T17:38:26.362895517Z"}
{"id":"NK-pumdm4","title":"get rid of the \"selected\" highlight thing","description":"the legacy version doesn't do that and i find it distracting, j/k just move things up/down","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T20:27:51.434041661Z","updated":"2026-02-13T22:37:06.185341246Z"}
{"id":"NK-qwef98","title":"UI Styling: Controls \u0026 Header","description":"","type":"","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-13T18:05:18.450759919Z","updated":"2026-02-13T18:11:46.291830432Z"}
{"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":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-14T16:45:04.739162003Z","updated":"2026-02-14T16:45:04.739162003Z"}
@@ -70,6 +71,7 @@
{"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":"open","priority":2,"labels":null,"assignee":"","created":"2026-02-14T17:10:02.185477382Z","updated":"2026-02-14T17:10:02.185477382Z"}
{"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"}
{"id":"NK-shpyxh","title":"add search to new ui","description":"","type":"epic","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T19:29:44.251257089Z","updated":"2026-02-14T01:02:58.547025683Z"}
+{"id":"NK-sk6pym","title":"fix docker-compose","description":"bug when trying to build in docker -- we may want to add an automated test for this later (though it may be hard since we're building in a vm to nest these)\n\n--\u003e 668e2e2c4b44\n[2/3] STEP 3/9: WORKDIR /app\n--\u003e b42c7c265b7f\n[2/3] STEP 4/9: COPY go.mod go.sum ./\n--\u003e 093e4d9b623e\n[2/3] STEP 5/9: RUN go mod download\n--\u003e 208c8aaac5eb\n[2/3] STEP 6/9: COPY . .\n--\u003e 7c44260c3ac0\n[2/3] STEP 7/9: COPY --from=frontend-builder /app/frontend/dist ./frontend/dist\n--\u003e 09749e6660e1\n[2/3] STEP 8/9: RUN rice -i ./web embed-go\n2026/02/14 17:34:13 no calls to rice.FindBox() found\n--\u003e cdc88c64da36\n[2/3] STEP 9/9: RUN go build -o neko .\nno Go files in /app\nError: building at STEP \"RUN go build -o neko .\": while running runtime: exit status 1","type":"bug","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-14T17:38:05.696339994Z","updated":"2026-02-14T17:38:05.696339994Z"}
{"id":"NK-sne5ox","title":"Implement Export/Import UI","description":"Add UI in settings to download OPML export and upload OPML import. Use /export/ and /import/ (need to check if import exists).","type":"epic","status":"icebox","priority":3,"labels":null,"assignee":"","created":"2026-02-13T15:05:23.266731399Z","updated":"2026-02-13T15:05:23.266731399Z"}
{"id":"NK-sxcm7y","title":"Enable Gzip Compression in Go Backend","description":"Check if the Go backend is serving content with gzip compression. If not, implement it to reduce page size and improve performance. Add tests to verify.","type":"feature","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T21:57:24.578388732Z","updated":"2026-02-13T22:22:49.350223751Z"}
{"id":"NK-t0nmbj","title":"new web frontend","description":"The current frontend uses an old version of backbone and jquery. Let's \"deprecate\" it -- keep it arouond so we can test against it and use it, but let's be able to also serve and use a nice shiny new frontend written in either simiple, highly efficient vanilla javascript, or put together something in react or similar. Needs to feel fast and low latency!\n\nIt's very important that this new frontend has all the functionality of the existing one AND looks similar (use same style, etc, but adjust a little if needed.)\n\nALSO make it highly testable and have high test coverage as you go. I don't want it to use the Chrome browser plugin thing, just test it on your own using things from the command line you can do.","type":"epic","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T02:01:37.2107893Z","updated":"2026-02-13T05:43:47.613995925Z"}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 7943f60..1c023c4 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -106,8 +106,10 @@ function App() {
localStorage.setItem('neko-theme', newTheme);
};
+ const basename = window.location.pathname.startsWith('/v2') ? '/v2' : '/';
+
return (
- <BrowserRouter basename="/v2">
+ <BrowserRouter basename={basename}>
<Routes>
<Route path="/login" element={<Login />} />
<Route
diff --git a/web/routing_test.go b/web/routing_test.go
new file mode 100644
index 0000000..972b208
--- /dev/null
+++ b/web/routing_test.go
@@ -0,0 +1,75 @@
+package web
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "adammathes.com/neko/config"
+)
+
+func TestRouting(t *testing.T) {
+ config.Config.DigestPassword = "secret"
+ router := NewRouter(&config.Config)
+
+ tests := []struct {
+ name string
+ path string
+ method string
+ cookie *http.Cookie
+ expectedStatus int
+ containsBody string
+ }{
+ {
+ name: "Root serves new UI",
+ path: "/",
+ method: "GET",
+ expectedStatus: http.StatusOK,
+ containsBody: "<!doctype html>", // from React dist/v2
+ },
+ {
+ name: "/v2/ serves new UI",
+ path: "/v2/",
+ method: "GET",
+ expectedStatus: http.StatusOK,
+ containsBody: "<!doctype html>",
+ },
+ {
+ name: "/v1/ redirects unauthenticated",
+ path: "/v1/",
+ method: "GET",
+ expectedStatus: http.StatusTemporaryRedirect,
+ },
+ {
+ name: "/v1/ serves legacy UI when authenticated",
+ path: "/v1/",
+ method: "GET",
+ cookie: authCookie(),
+ expectedStatus: http.StatusOK,
+ containsBody: "<title>neko rss mode</title>", // from legacy ui.html
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := httptest.NewRequest(tt.method, tt.path, nil)
+ if tt.cookie != nil {
+ req.AddCookie(tt.cookie)
+ }
+ rr := httptest.NewRecorder()
+ router.ServeHTTP(rr, req)
+
+ if rr.Code != tt.expectedStatus {
+ t.Errorf("Expected status %d, got %d", tt.expectedStatus, rr.Code)
+ }
+
+ if tt.containsBody != "" {
+ body := strings.ToLower(rr.Body.String())
+ if !strings.Contains(body, strings.ToLower(tt.containsBody)) {
+ t.Errorf("Expected body to contain %q, but it didn't", tt.containsBody)
+ }
+ }
+ })
+ }
+}
diff --git a/web/web.go b/web/web.go
index 11a5831..6c8e632 100644
--- a/web/web.go
+++ b/web/web.go
@@ -223,8 +223,14 @@ func NewRouter(cfg *config.Settings) http.Handler {
mux.Handle("/static/", GzipMiddleware(http.StripPrefix("/static/", http.FileServer(http.FS(staticSub)))))
// New Frontend (React/Vite) from web/dist/v2
+ // Default route
+ mux.Handle("/", GzipMiddleware(http.HandlerFunc(ServeFrontend)))
+ // Also keep /v2/ for explicit access
mux.Handle("/v2/", GzipMiddleware(http.StripPrefix("/v2/", http.HandlerFunc(ServeFrontend))))
+ // Legacy UI at /v1/
+ mux.Handle("/v1/", GzipMiddleware(http.StripPrefix("/v1/", AuthWrap(http.HandlerFunc(indexHandler)))))
+
// New REST API
apiServer := api.NewServer(cfg)
// We need to mount the raw mux from apiServer if we want /api/ access,
@@ -257,7 +263,7 @@ func NewRouter(cfg *config.Settings) http.Handler {
mux.HandleFunc("/api/logout", apiLogoutHandler)
mux.HandleFunc("/api/auth", apiAuthStatusHandler)
- mux.Handle("/", GzipMiddleware(AuthWrap(http.HandlerFunc(indexHandler))))
+ // Removed default root handler for legacy UI
return SecurityHeadersMiddleware(CSRFMiddleware(mux))
}