Commit Graph

24 Commits

Author SHA1 Message Date
khannurien
364c498a24 v3: dependencies upgraded and deno audit clean, every API response typed end to end, no explicit any left — and the site search stops handing out password hashes — deno audit reported 19 advisories (11 high); none remain. nodemailer 9 → 10.0.10 closes four of them (a quadratic address parser, two recipient-domain allow-list bypasses, resolveContent ignoring disableFileAccess/disableUrlAccess); 10's one break is Node ≥ 20, and 10.0.11 is a day too young for the minimum release age. react-router 8.0.1 → 8.4.0 closes the RSC-mode CSRF bypass. Refreshing the lockfile clears the transitive ones that came in through Vite, ESLint and Lingui: postcss, nanoid, picomatch, browserslist, brace-expansion and baseline-browser-mapping. Everything else moves to its latest too — Lingui 6.8 (its SWC plugin 6.7), React 19.3, Vite 8.3.1, react-hook-form 7.89, marked, jose, ESLint 10.11, typescript-eslint 8.70, the @types — and frimousse 0.3 → 0.4, whose declarations only add (custom emoji data resolvers, a cache). TypeScript stays on 6.0: typescript-eslint supports < 6.1, so 7.0 would stop the lint. allowScripts names @swc/core 1.16.2 and drops esbuild, which Vite 8 no longer installs.
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 51s
Search: GET /api/search sent each matching user's full row — password hash and email included — to anyone, signed in or not, because searchUsersGlobal returned User[] and the route passed it through. It returns profiles only now, stripped where the rows are read, and the search test fails on the old code. Types alone wouldn't have caught it (a User is structurally a PublicUser), so the strip is explicit and tested.

Typed routes. Every JSON response is declared as APIResponse<T>, the way the typed routes already did, with the missing shapes named in api/model/interfaces.ts: PublicUser (no hash, no email) and SelfUser (your own profile, with email) plus toPublicUser/toSelfUser in place of ten hand-written destructurings, AuthTokens, AuthResponse, MediaTokenResponse, InviteResponse, AttachmentResponse, SearchResults, LinkPreview, UserSuggestion and Invitee. Request bodies are `unknown` and narrowed by the existing guards, or by a new fieldOf(obj, key) where a route reads one field — no more reading off Oak's `any` or casting to Record<string, unknown>. The password-reset routes answer `{ success: true, data: null }` rather than a bare `{ success: true }`, so every success fits the envelope; the frontend only reads `success`.

Socket protocol: routes/ws.ts had its own send() taking Record<string, unknown>, so nothing checked what it sent. It takes ServerToClientMessage now, which turned up two mismatches: `pong` was missing from the union although the server answers every client ping with one, and vote_ack carried a `success: true` no type declares and no client reads. The union has PongMessage and vote_ack lost the field.

No explicit any. The YouTube playlist parse reads ytInitialData as `unknown` through a small at(node, …path) that checks each step, instead of `type Json = any`. The backend test helpers type the response body as the server's envelope: request<T>() and get/post/put/patch/del<T>() name what `data` holds, res.data throws with the server's message when the call didn't succeed and res.error when it didn't fail, and each test says which API type it expects, so a test reading a field the route doesn't send no longer compiles. Assertions that a field is absent are `assertFalse("passwordHash" in …)` rather than reading a field the type says isn't there. The socket tests' messages are ServerToClientMessage, with next()/all() keyed by type so each returns that message's shape.

No double casts in src/ either. window.YT and onYouTubeIframeAPIReady are declared on Window; the test's fake player is a class forwarding to the spies the tests inspect. WSContext, FollowContext and PlayerContext export their defaults (defaultWSValue, defaultFollowValue, defaultPlayerValue), which the tests import instead of reading React's private _currentValue. handleMentionKeyDown takes a MentionKey — the key fields it reads — so the test builds a plain object; useUserDumpFeed declares sentinelRef as the callback ref it is. In src/test/setup.ts the IntersectionObserver fake implements IntersectionObserver, and report(isIntersecting, top?) builds complete entries in place of partial ones cast in five files; the Animation fake implements Animation; the canvas getContext stub goes in through defineProperty. The XHR fake's last request is optional behind a getter that fails if none was made, the keyframes are checked with matchers, and the roleless legacy session is built by leaving the role out. One test changes meaning slightly: "leaving the view…" relied on entries with no position at all, and now leaves and re-enters at the same height, which is what it describes. The one cast left is in api/test/ws.test.ts: deno.json's "dom" lib hides Deno's WebSocket overload that takes handshake headers, and dropping that lib would break the editor's view of src/; it's commented and names the real signature.

Also: the notifications test answered a Response-typed promise with a plain object, which failed `tsc -b`; it uses the fetch stub's deferred() now. ESLint didn't read the `deno-lint-ignore no-explicit-any` comments, so `eslint .` failed on three files; those anys are gone. `deno fmt` over api/ and src/ (not the compiled src/locales/*.js); the reflow left all 332 message ids intact, and the .po files only have their source line references updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:05:29 +00:00
khannurien
5fd1d364da v3: the header stops cropping itself and the search stops losing its place — a review of the header, search bar and presence row turned up tabs cut off at both ends, avatars that could only be all there or gone, and a search that forgot what it was showing. Between 860px and about 1000px, and again just past 1150px where the username and Playlists links arrive, presence plus tabs ran wider than the header's centre column; the slot couldn't shrink, so the column clipped it on both sides, and logged in FOLLOWED went missing with one person online. The container queries meant to shed presence under 460px and tabs under 280px never fired, overridden by later rules of the same weight, and no fixed width would have been right anyway: the tabs run from 314px (nyt, English) to 485px (melange, French). The slot can now shrink, and the presence row sizes itself through a new useRoomBeside, which measures the room the tabs leave it in the header and in the row under it: as many avatars as fit, at most six seats, the rest folded into a "+N" counter (square in brutalist), and never fewer than an avatar and the counter — below that the tabs scroll instead. So presence shows at every width, phones included, where a 512px rule used to hide it. When the header's tabs do scroll, they get the app's right-edge fade in the header's own colour, only while they overflow, and the active tab is scrolled clear of it. The avatars fan out on hover only where there's a pointer, so a tap doesn't leave them spread, and the label-less "+" button squares up with the icons beside it on small screens. Search: the ✕ on results opened directly ran navigate(-1) out of the app and now goes home; after several searches it stepped back one query at a time and now returns to where the search began in one step, carried in the entry's state, which useTabParam now keeps across result tabs; those tabs (/search/~/users) weren't recognised as the results page at all. On results the bar stays open holding the current query, including after a new search or Back, where it used to collapse and empty. The input no longer grabs focus on a results page, so a phone's keyboard doesn't cover the results, and submitting puts it away. Escape closes an opened search and returns focus to its button, or on results reverts an edited query, overriding the search input's own clear-on-Escape. The two search bars share one draft, so crossing the breakpoint keeps it.
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 44s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:00:15 +00:00
khannurien
8c3efe6c30 v3: inline edit forms and the narrow-viewport search row ease open instead of popping in
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:26:18 +00:00
khannurien
6c4f410d9b v3: added a chatbox
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
2026-06-29 20:25:10 +00:00
f171027673 v3: added customizable site emoji (logo), small visual tweaks
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
2026-06-29 15:12:25 +02:00
khannurien
a1b71ad0c8 v3: consistent tabs url across app, maxy small fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
2026-06-22 17:25:40 +00:00
khannurien
b822f861ed v3: lots of small UI tweaks and fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
2026-04-11 08:35:16 +00:00
khannurien
9c889a9531 v3: fixed search in prod, lots of UI fixes across the app
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
2026-04-09 21:54:07 +00:00
khannurien
ed7695663e v3: correctly using SITE_NAME across the app, added notifications on comments
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 44s
2026-04-08 20:12:30 +00:00
khannurien
1321e374bf v3: performance pass, bundle size pass, i18n pass, docker pass 2026-04-08 13:19:39 +00:00
khannurien
a69788c15b v3: localization fixes, char counters & limits on all text fields, ux fixes 2026-04-03 19:47:37 +00:00
khannurien
0ce80398a4 v3: added localization, use global player for uploaded audio/video files 2026-04-03 15:29:33 +00:00
khannurien
cbb3505139 v3: search engine, responsive header with compact user menu 2026-03-29 11:56:31 +00:00
khannurien
f0f6472db6 v3: added attachments to resources, allow users to paste images into TextEditor, strengthened WS reliability 2026-03-27 08:53:32 +00:00
khannurien
0cb5a798c7 v3: added journal view to the index, code organization pass 2026-03-25 21:07:17 +00:00
khannurien
c293f3e706 v3: code quality pass 2026-03-24 18:47:05 +00:00
khannurien
fbbbb43258 v3: code quality pass, various bug fixes 2026-03-23 07:47:49 +00:00
khannurien
34e908d1bc v3: added content slugs, fixed real-time updates in client, added @mentions across the app, added new file selector and drop zone 2026-03-22 16:06:26 +00:00
khannurien
5bed03baa5 v3: error cards across the app, friendly network errors, code quality pass 2026-03-21 19:17:23 +00:00
khannurien
608c6bc6a8 v3: follows, notifications, invite-only registration, unread markers 2026-03-21 18:42:47 +00:00
khannurien
7c098e7c4c v2: global player, infinite scroll, image picker, threaded comments 2026-03-21 13:55:22 +00:00
khannurien
be426eb150 v1 feature: added playlists 2026-03-16 16:52:53 +00:00
khannurien
867e64cb5b v1 review pass: fixed some minor bugs 2026-03-16 11:08:39 +00:00
khannurien
e88fed4e98 vibe coded v1 2026-03-16 07:34:49 +00:00