web/src/app.jsx was written against an earlier shape of the API. Checked against the server on eceb5f2:
/mode sends {"command":"MODE","to":"#chan","params":["+m"]}. The server reads the change from body (handleChannelMode in internal/handlers/api.go), so a MODE with no body is a query: it answers 324 and 329 and changes nothing. The client's /help promises "Set channel modes"; no mode can be set from the web client.
processMessage matches numeric replies on "353", "332", "322", "323", "352", "315", "311", "312", "319", "318", "375", "372" and "376". The server sends the reply's name in command and its number in code (scanMessages in internal/db/queries.go; README.md, Numeric Reply Codes), for example {"command":"RPL_NAMREPLY","code":353,...}. None of the cases matches, and the default branch prints body alone. So /list prints each channel's topic without its name, and nothing for a channel with no topic; /who prints 0 nick per member; /whois prints the nick by itself; the @ and + prefixes in 353 are never read.
UserList groups members by m.mode ("o", "v"), but GET /api/v1/channels/{name}/members returns isOperator and isVoiced, so the member list never shows @ or +.
The poll loop treats a 401 as a network error and retries every 3 s for ever; after a QUIT from another client, or session expiry, the page shows "Reconnecting" indefinitely. README.md (Client Development Guide, Reconnection) says a 401 means a new session.
LoginScreen stores res.token, a field the session response does not have (README.md: no credential in the body), so localStorage holds the string undefined and every request carries Authorization: Bearer undefined. The client works only because the browser sends the cookie. Harmless, and worth removing in the same change.
Fix: send MODE as body: [change, value]; switch on msg.code; when reading 353 over the HTTP API take the nick before !, since the body carries hostmasks (README.md, 353 example); map isOperator and isVoiced to the prefixes; on 401 clear local state and show the login screen; drop the token code. The stored key is also what makes the page resume a session on reload (LoginScreen probes /state only when it is present), so the resume must then run unconditionally and fall through to the form on 401. The web build has no test runner, so the PR says how each item was checked in a browser against a server built from the branch.
Definition of done
/mode +m on a channel the user operates sets the mode (the next /mode query shows it).
/list, /who and /whois show channel names with counts, nick and flags, and the WHOIS lines, taken from the named numerics.
Operators and voiced members show @ and + in the member list.
A 401 on the poll returns the page to the login screen.
No Authorization header and no neoirc_token in localStorage; a reload still resumes the session.
One reviewed PR against next.
Model: fable-5-1
`web/src/app.jsx` was written against an earlier shape of the API. Checked against the server on `eceb5f2`:
- `/mode` sends `{"command":"MODE","to":"#chan","params":["+m"]}`. The server reads the change from `body` (`handleChannelMode` in `internal/handlers/api.go`), so a MODE with no body is a query: it answers 324 and 329 and changes nothing. The client's `/help` promises "Set channel modes"; no mode can be set from the web client.
- `processMessage` matches numeric replies on `"353"`, `"332"`, `"322"`, `"323"`, `"352"`, `"315"`, `"311"`, `"312"`, `"319"`, `"318"`, `"375"`, `"372"` and `"376"`. The server sends the reply's name in `command` and its number in `code` (`scanMessages` in `internal/db/queries.go`; `README.md`, Numeric Reply Codes), for example `{"command":"RPL_NAMREPLY","code":353,...}`. None of the cases matches, and the default branch prints `body` alone. So `/list` prints each channel's topic without its name, and nothing for a channel with no topic; `/who` prints `0 nick` per member; `/whois` prints the nick by itself; the `@` and `+` prefixes in 353 are never read.
- `UserList` groups members by `m.mode` (`"o"`, `"v"`), but `GET /api/v1/channels/{name}/members` returns `isOperator` and `isVoiced`, so the member list never shows `@` or `+`.
- The poll loop treats a 401 as a network error and retries every 3 s for ever; after a QUIT from another client, or session expiry, the page shows "Reconnecting" indefinitely. `README.md` (Client Development Guide, Reconnection) says a 401 means a new session.
- `LoginScreen` stores `res.token`, a field the session response does not have (`README.md`: no credential in the body), so localStorage holds the string `undefined` and every request carries `Authorization: Bearer undefined`. The client works only because the browser sends the cookie. Harmless, and worth removing in the same change.
Fix: send MODE as `body: [change, value]`; switch on `msg.code`; when reading 353 over the HTTP API take the nick before `!`, since the body carries hostmasks (`README.md`, 353 example); map `isOperator` and `isVoiced` to the prefixes; on 401 clear local state and show the login screen; drop the token code. The stored key is also what makes the page resume a session on reload (`LoginScreen` probes `/state` only when it is present), so the resume must then run unconditionally and fall through to the form on 401. The web build has no test runner, so the PR says how each item was checked in a browser against a server built from the branch.
## Definition of done
1. `/mode +m` on a channel the user operates sets the mode (the next `/mode` query shows it).
2. `/list`, `/who` and `/whois` show channel names with counts, nick and flags, and the WHOIS lines, taken from the named numerics.
3. Operators and voiced members show `@` and `+` in the member list.
4. A 401 on the poll returns the page to the login screen.
5. No `Authorization` header and no `neoirc_token` in localStorage; a reload still resumes the session.
6. One reviewed PR against `next`.
Model: fable-5-1
clawbot
self-assigned this 2026-10-20 16:51:34 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
web/src/app.jsxwas written against an earlier shape of the API. Checked against the server oneceb5f2:/modesends{"command":"MODE","to":"#chan","params":["+m"]}. The server reads the change frombody(handleChannelModeininternal/handlers/api.go), so a MODE with no body is a query: it answers 324 and 329 and changes nothing. The client's/helppromises "Set channel modes"; no mode can be set from the web client.processMessagematches numeric replies on"353","332","322","323","352","315","311","312","319","318","375","372"and"376". The server sends the reply's name incommandand its number incode(scanMessagesininternal/db/queries.go;README.md, Numeric Reply Codes), for example{"command":"RPL_NAMREPLY","code":353,...}. None of the cases matches, and the default branch printsbodyalone. So/listprints each channel's topic without its name, and nothing for a channel with no topic;/whoprints0 nickper member;/whoisprints the nick by itself; the@and+prefixes in 353 are never read.UserListgroups members bym.mode("o","v"), butGET /api/v1/channels/{name}/membersreturnsisOperatorandisVoiced, so the member list never shows@or+.README.md(Client Development Guide, Reconnection) says a 401 means a new session.LoginScreenstoresres.token, a field the session response does not have (README.md: no credential in the body), so localStorage holds the stringundefinedand every request carriesAuthorization: Bearer undefined. The client works only because the browser sends the cookie. Harmless, and worth removing in the same change.Fix: send MODE as
body: [change, value]; switch onmsg.code; when reading 353 over the HTTP API take the nick before!, since the body carries hostmasks (README.md, 353 example); mapisOperatorandisVoicedto the prefixes; on 401 clear local state and show the login screen; drop the token code. The stored key is also what makes the page resume a session on reload (LoginScreenprobes/stateonly when it is present), so the resume must then run unconditionally and fall through to the form on 401. The web build has no test runner, so the PR says how each item was checked in a browser against a server built from the branch.Definition of done
/mode +mon a channel the user operates sets the mode (the next/modequery shows it)./list,/whoand/whoisshow channel names with counts, nick and flags, and the WHOIS lines, taken from the named numerics.@and+in the member list.Authorizationheader and noneoirc_tokenin localStorage; a reload still resumes the session.next.Model: fable-5-1