Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
DNS_DOMAIN=localhost
DNS_DOMAIN=localhost
# Keycloak client for the /appeal login. Must match the client TripBot redeems
# the code with (KEYCLOAK_BAN_APPEALS_CLIENT_ID). Read when `next dev` starts or
# `next build` runs, not by a running production container.
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ updates:
mui:
patterns:
- "@mui/*"
# jest and jest-environment-jsdom share Jest's internal packages. Bumping
# one alone (#256) installs a second copy of them, so bump them together.
jest:
patterns:
- "jest"
- "jest-*"
- "@jest/*"
- "@types/jest"
ignore:
# typescript-eslint supports only typescript <6.1.0 (as of 8.72.1), so
# TypeScript 7 fails npm ci (#222). Remove once typescript-eslint
Expand Down
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modul
# Copy over the existing source code
COPY --chown=node:node . .

# Next.js writes `process.env.NEXT_PUBLIC_*` into the browser bundle as literal
# values during `next build`, so they must be set here, at build time. Setting
# them on the running container has no effect on client code. Pass them with
# `docker build --build-arg` or `build.args` in compose. An unset arg becomes an
# empty string, and the code then uses its fallback (see src/utils/keycloak.ts).
ARG NEXT_PUBLIC_DNS_DOMAIN
ARG NEXT_PUBLIC_KEYCLOAK_URL
ARG NEXT_PUBLIC_KEYCLOAK_CLIENT_ID
ENV NEXT_PUBLIC_DNS_DOMAIN=$NEXT_PUBLIC_DNS_DOMAIN \
NEXT_PUBLIC_KEYCLOAK_URL=$NEXT_PUBLIC_KEYCLOAK_URL \
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=$NEXT_PUBLIC_KEYCLOAK_CLIENT_ID

RUN npx next build

###################
Expand Down
114 changes: 109 additions & 5 deletions REVIEW-ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,24 @@ Priorities: **P0** exploitable or critical, fix now · **P1** real bug or exposu
| WEB-024 | P3 | perf | Five UI/CSS systems and four icon sets ship together |
| WEB-025 | P3 | lint | Two `react-hooks/set-state-in-effect` warnings |
| WEB-026 | P3 | deps | Dev-only advisories remain (brace-expansion, js-yaml, zod) |
| WEB-027 | P2 | deploy | Both Drone servers build every push; the other server's build hangs as pending |
| WEB-028 | P2 | deploy | The shared deployer gives up after ~60 s when another deploy is running |
| WEB-029 | P3 | bug | Factsheets pager shows the full count ("1-10 of 555") after filtering |
| WEB-030 | P2 | perf | Expanding a factsheet row keeps the browser busy for tens of seconds |
| WEB-031 | P1 | auth, build | Ban-appeal login fails: the Keycloak client ID never reaches `next build` |

### Overlap groups (same files; take together or in sequence)

- **Lockfile:** WEB-001 first, then anything else that changes `package.json` or
`package-lock.json` (WEB-016, WEB-019, WEB-024, WEB-026).
- **Template scripts:** WEB-008, WEB-009, WEB-022 (`_document.tsx`, `_app.tsx`, `public/assets/vendor`).
- **Drug data:** WEB-007, WEB-010 (`_app.tsx`, `Factsheets.tsx`, `factsheets/[drug].tsx`).
- **Auth:** WEB-005, WEB-006, WEB-025 (`keycloak.ts`, `ProfileButton.tsx`, `appeal.tsx`).
- **Drug data:** WEB-007, WEB-010, WEB-029, WEB-030 (`_app.tsx`, `Factsheets.tsx`,
`factsheets/[drug].tsx`, `DrugInfo.tsx`).
- **Auth:** WEB-005, WEB-006, WEB-025, WEB-031 (`keycloak.ts`, `ProfileButton.tsx`, `appeal.tsx`).
- **Homepage:** WEB-003, WEB-020, WEB-021 (`pages/index.tsx`, `Counter.tsx`, `Ghost.tsx`).
- **Workflows:** WEB-012, WEB-013, WEB-015, WEB-017 (`.github/workflows/`).
- **Docker/Drone:** WEB-014, WEB-016, WEB-018 (`.drone.yml`, `Dockerfile`, `.dockerignore`).
- **Docker/Drone:** WEB-014, WEB-016, WEB-018, WEB-027, WEB-028, WEB-031 (`.drone.yml`,
`Dockerfile`, `.dockerignore`, Drone and deployer settings on the servers).

---

Expand Down Expand Up @@ -207,7 +214,11 @@ and pass `code_verifier` to the token endpoint. Quote and validate the avatar UR
### WEB-007 — `QueryClient` recreated on every render

- Priority: P1 · Area: perf
- Status: open
- Status: fixed — fix/WEB-007-query-client (#264)
- Notes: `_app.tsx` keeps one `QueryClient` in `useState`. The table and the factsheet pages
share one `useDrugs()` hook (`src/utils/drugs.ts`, key `["drugs"]`, 30 min `staleTime`), so
`drugs.json` downloads once per page load. Their old queries used the same key with different
data shapes, which would have clashed in a shared cache. Also fixes WEB-029.
- Files: `src/pages/_app.tsx:48`, `src/components/Factsheets.tsx:418`

**Problem.** `<QueryClientProvider client={new QueryClient()}>` builds a new client each time
Expand Down Expand Up @@ -313,6 +324,8 @@ does), add `permissions: contents: read` at the top of every workflow, and grant

- Priority: P2 · Area: deploy
- Status: open
- Notes: `docker-compose.website-prod.yml` lives on the UAT server; `DISCORD_GUILD_ID` was
added to it for #258. How the Drone step's checkout reaches it is still undocumented.
- Files: `.drone.yml:72-125`

**Problem.**
Expand Down Expand Up @@ -481,7 +494,14 @@ in `eslint.config.mjs` as known debt).
### WEB-026 — Dev-only advisories remain

- Priority: P3 · Area: deps
- Status: open
- Status: fixed — chore/WEB-026-update-deps (#265)
- Notes: on 2026-11-08 (`uat` at `7963e49`) `npm audit` reports 2 high, 27 moderate and 3 low,
from `brace-expansion`, `zod`, `minimatch`, `glob` and `deepmerge`; `--omit=dev` reports none.
After the upgrade the highs are gone; 27 moderate and 3 low remain, all under eslint and
jest. `minimatch` and `glob` are fixed only in new majors (11 and 14), which eslint and jest
don't accept yet; minimatch 11 changes `partial` matching, which eslint uses to walk
directories, so overriding could silently change which files get linted. `deepmerge` has no
fixed release. Recheck when eslint or jest move to minimatch 11.
- Files: `package-lock.json`

**Problem.** After WEB-001, full `npm audit` still reports 3 high and 19 moderate advisories,
Expand All @@ -492,3 +512,87 @@ churn for the security PR, so it was left out.

**Fix.** In a separate PR, run `npm audit fix` (no `--force`), then lint, test and build.
Belongs to the lockfile overlap group.

### WEB-027 — Both Drone servers build every push

- Priority: P2 · Area: deploy
- Status: open
- Files: `.drone.yml`; repository settings on drone.tripsit.io and drone.tripsit.me

**Problem.** The repository is active on two Drone servers. drone.tripsit.io has the UAT
runner (`env: uat`) and drone.tripsit.me has the production runner (`env: prod`). Both
receive every push and both start a build. The one without a matching runner keeps it
pending until the next push skips it (for example .me #115 and #117 for `uat`, .io #35 and
#39 for `main`). Both report under the same GitHub status, `continuous-integration/drone/push`,
so whichever server reports last sets the commit's status. If Drone kills a pending build
(.me #109 was "killed"), the commit can show as failed even though its deploy worked.

**Fix.** Keep each branch's pipeline on one server: deactivate the repo on the server that
doesn't deploy that branch, or give the servers different status names. If both must stay
active, check how each server handles builds it can't run.

### WEB-028 — The shared deployer gives up after about 60 s when another deploy is running

- Priority: P2 · Area: deploy
- Status: open
- Notes: #259 makes the website's production step retry the "busy" answer (6 attempts, 30 s
apart). TripBot's `.drone.yml` needs the same change.
- Files: deployer on the production server (`docker/deploy/`, not in this repo); `.drone.yml`

**Problem.** The website and TripBot deploy through one deployer (`http://deployer:9000`),
which runs one deployment at a time. A deploy takes about 4.5 minutes, but a second request
waits only about 60 s and then fails with `DEPLOY_RESULT=failure (deployer busy; no deployment
started)`. On 2026-11-08 this failed the website's production build #118 while TripBot #724
was deploying. It had to be restarted by hand.

**Fix.** Raise the deployer's wait for its lock above the longest deploy, or queue requests
on the server. Until then, keep the busy-retry in each pipeline.

### WEB-029 — Factsheets pager ignores filters

- Priority: P3 · Area: bug
- Status: fixed — fix/WEB-007-query-client (#264)
- Files: `src/components/Factsheets.tsx`

**Problem.** The table passed `rowCount: meta.totalRowCount` (all 555 drugs) to
material-react-table. `rowCount` is meant for server-side pagination, so the footer showed
"1-10 of 555" even when a filter left one row. Production showed the same.

**Fix.** Drop `rowCount`; the table counts filtered rows itself.

### WEB-030 — Expanding a factsheet row keeps the browser busy

- Priority: P2 · Area: perf
- Status: open
- Files: `src/components/Factsheets.tsx`, `src/components/DrugInfo.tsx`

**Problem.** After a row on `/factsheets` is expanded, its detail cards appear in about
0.4 s, but Chrome's renderer stays busy afterwards: screenshots time out for 30–60 s and one
measured frame took about 2 s. tripsit.me (MUI 6) and tripsit.io (MUI 9) behave the same.
The cause hasn't been investigated; `DrugInfo` renders around 50–130 MUI `Grid` items per
drug, and the table is wrapped in several providers and animations.

**Fix.** Profile an expansion in Chrome DevTools (Performance panel) to find what keeps the
main thread or compositor busy, then fix that.

### WEB-031 — Ban-appeal login fails: the Keycloak client ID never reaches `next build`

- Priority: P1 · Area: auth, build
- Status: fixed — fix/WEB-031-keycloak-build-args (#266)
- Notes: Also needs `build.args` for the three `NEXT_PUBLIC_*` variables in the compose files
that build the image: `docker-compose.website-prod.yml` on the UAT server (WEB-014) and
`docker-compose.website.yml` in the production server's repo. Production builds from `main`.
- Files: `Dockerfile`, `src/utils/keycloak.ts`

**Problem.** Logging in at `/appeal` fails after the user signs in. TripBot logs "Keycloak
token error" and `POST /api/v2/keycloak/token` returns 500. Keycloak binds an auth code to
the client that requested it, and TripBot redeems it as the ban-appeals client
(`KEYCLOAK_BAN_APPEALS_CLIENT_ID`). The browser asked as `website`. Next.js writes
`NEXT_PUBLIC_*` values into the browser bundle during `next build`, but the Dockerfile never
declared them, so the build saw none. The live bundle still has
`process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID||"website"` and always uses the fallback.

**Fix.** Declare the three variables as `ARG`/`ENV` in the Dockerfile's build stage and pass
them as compose `build.args`. `keycloak.ts` no longer falls back to `website`: a missing
client ID throws outside production and is logged in production, where Keycloak then rejects
the request before the user signs in.
Loading
Loading