Skip to content

Admin interface, Blueprints catalog, and site visual refresh#17

Merged
tastybento merged 20 commits into
developfrom
feature/admin-interface-and-site-refresh
Apr 26, 2026
Merged

Admin interface, Blueprints catalog, and site visual refresh#17
tastybento merged 20 commits into
developfrom
feature/admin-interface-and-site-refresh

Conversation

@tastybento

Copy link
Copy Markdown
Member

Summary

Bundles 16 commits of accumulated work on develop into a single PR for review. The changes group into four themes:

Blueprints catalog (new feature)

  • Add Blueprints tab backed by a local clone of the BentoBoxWorld/weblink repo, refreshed on the same 6-min cron used for addon polling
  • Discord OAuth (identify scope) + signed-session cookies + CSRF + rate-limited blueprint submissions that open PRs against weblink
  • Server-side image serving with a path-traversal guard and PNG-only whitelist
  • /submit page redesigned in the navy-paper sub-brand, live preview that renders the actual blueprint, file hand-off from the Blueprints upload tile
  • CLAUDE.md documents the catalog + weblink sync

Admin interface

  • Foundation: shell, route guards, and shared layout
  • Admins tab: promote/demote admins by Discord ID
  • Presets tab: reorder and edit homepage presets
  • Addons tab: full CRUD plus per-addon version mappings
  • Recent tab: audit log, reset-to-baseline, and Export-PR

Site visual refresh

  • Paper aesthetic across Landing, Blueprints, Custom builder, and Third-Party catalog
  • Landing copy updated for Mojang's switch to year-based Minecraft versions (MC 1.15–26.1.x, 26.1 added to the Compatible-with badges)
  • Removed the Linux-only unzip … -d plugins/ snippet from the Quickstart card

Misc

  • yarn prime-cache script to seed JarCache.sqlite in one shot
  • .gitignore for macOS metadata, runtime data/ directory, and design bundles

Test plan

  • yarn build succeeds
  • yarn site boots; landing renders with the new Compatible-with badges (26.1 first) and no unzip snippet
  • /blueprints lists catalog entries from data/weblink/; downloading a single blueprint and a multi-select zip both work
  • Discord OAuth round-trip on /submit; submission opens a PR against BentoBoxWorld/weblink
  • Admin interface: each tab (Admins, Presets, Addons, Recent) reachable behind the route guard and round-trips to the API
  • CI green on Node 12.x / 14.x / 16.x

🤖 Generated with Claude Code

tastybento and others added 16 commits April 24, 2026 08:38
Periodically git-clones BentoBoxWorld/weblink to data/weblink, parses
.blueprint and bundle .json files, derives per-blueprint stats
(dimensions, block/entity counts, biome list, sinking flag) and serves
them via new /api/blueprints, /api/blueprints/download and
/api/blueprints/zip endpoints. Front-end adds a Blueprints tab with
game-mode filter, per-card download, and multi-select zip download.

The optional blueprints/catalog.json overlay in weblink supplies
curation fields (displayName, description, author, tags, license,
image) that take precedence over whatever is in the .blueprint file.
Phase-2 submission sanitisers (strip BlueprintBundle.commands; reject
command-block materials and opaque inventory payloads) are included as
helpers for when the upload flow lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase A — Images:
  weblink/blueprints/images/<gameMode>/<name>.png is the canonical
  location. blueprintCatalog.buildCatalog() auto-detects images (and an
  optional .thumb.png variant) when the overlay doesn't specify one. A
  new /blueprints/images/* handler in src/index.ts serves the cloned
  tree with path-traversal guard and PNG-only whitelist.

Phase B — Discord OAuth:
  Server-side authorization-code flow in src/api/auth.ts (identify
  scope only). HttpOnly + signed session cookie via cookie-parser, with
  SameSite=Lax, Secure-in-prod, 30-day TTL, 5 concurrent sessions per
  user, and a daily prune cron. New SQLite at data/Auth.sqlite hosts
  User, Session, and Submission tables (src/api/models/auth.ts).
  Helmet config rebuilt with explicit CSP that allows Discord avatar
  CDN images while leaving top-level OAuth navigation unconstrained.

Phase C — Submissions:
  POST /api/blueprints/submit accepts a multipart upload (multer, 5 MB
  cap), JSON-parses, ajv-validates against the vendored
  blueprint.schema.json, runs validateBlueprintSubmission (rejects
  command-block materials + opaque inventory payloads), checks for a
  slug collision against the local weblink clone, then opens a PR
  against BentoBoxWorld/weblink via Octokit using
  env.weblink_github_token. The catalog.json patch credits the user's
  Discord profile and records the EPL-2.0 license. New
  /submit React page (login gate + form + ToS checkbox).

Phase D — Hardening:
  Per-session CSRF token returned by /api/me and required as
  X-Csrf-Token on every POST. Sliding-window in-memory rate limiters:
  30 OAuth callbacks/min/IP, 3 submissions/hour and 10/day per user.
  Discord access token never logged.

Phase E — Legal + account:
  /terms, /privacy and /account pages. Account page lists submissions,
  allows logout, and a confirm-then-delete CCPA-compliant account
  deletion path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… Third-Party

Implements the BentoBox Downloads design refresh end-to-end. Pulls every
public route into a single visual language (warm paper for the main brand,
navy paper for Blueprints) over a Minecraft-photo hero band.

- New Landing route at `/` with hero, quickstart card, lifetime stats strip,
  "why BentoBox" section, sponsor CTA, and an embedded presets carousel.
- New Blueprints page on a navy sub-brand surface with a hero band
  (`bp-paper.with-grid`), sticky filter rail (search, game-mode chips, tag
  chips), CSS-only isometric voxel previews derived from each blueprint's
  topBlocks, an inline "Submit your blueprint" upload tile that links to
  `/submit`, hover-expanded card detail, and a sticky multi-select download
  bar.
- Custom page rebuilt around the addon picker as the centrepiece. Lifetime
  download counter in the hero, per-row download stats, custom checkboxes,
  category/flat group toggle, sticky info rail with version-state pill
  (LATEST/BETA/OLDER), hover-driven description card, and a "Your bundle"
  card with running total downloads + Generate ZIP / copy-share URL.
- Third-Party page rebuilt with multi-select tag chips (AND filter), a
  results count strip, and a 2-col card grid with avatar tiles coloured
  from the addon's first tag.
- PresetsPage and PremadeCard adopt the paper card chrome, accent stripe,
  and per-addon coloured pill from the patch.
- Header/Navigation refreshed: transparent over the Landing hero,
  paper-blur once scrolled, mobile drawer, and Presets links to the
  in-page anchor on Landing rather than a standalone route. Standalone
  `/presets` route removed; Landing handles the hash on mount.
- New design-tokens.css adds the full `bb-*` token set (paper, ink,
  pills, buttons, shadows, mobile responsive helpers), the navy `bp-*`
  sub-brand block, `.bb-placeholder`, and a custom `.bb-check` checkbox.
- Hero backdrops live at `src/web/static/root/bg-*.{jpg,png}`.

All existing behavioural contracts preserved: `/api/generate?downloads=…
&version=…` URL byte-identical, `#["addon",…]` hash + `?v=` preselection,
copy-share URL re-hydrates, version-warning band semantics, third-party
tag-filter parity (extended additively to multi-select).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- `.DS_Store` everywhere (was previously tracked-in-error in some places).
- `/data/` — runtime auth DB and the auto-cloned weblink mirror.
- `*.sqlite` (replaces the old lower-case-only `jarcache.sqlite` /
  `downloads.sqlite` patterns, which didn't match `JarCache.sqlite` etc.
  on case-sensitive filesystems).
- `/design_handoff_*/` and `/BentoBox Download Site*.zip` so the design
  hand-off drops stop showing in `git status`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "Submit your blueprint" tile on /blueprints used to be a `<Link>` whose
drop handler did nothing — clicking anywhere just navigated to /submit
empty, and dragging a file over it was inert. Replaced with a real
dropzone:

- Click the tile (or the Choose-file pill) opens a native `<input
  type="file" accept=".blueprint">` picker.
- Drag-and-drop captures the file and lights the cyan accent border.
- Either path stashes the File via `setPendingBlueprint()` and SPA-pushes
  to /submit.
- `SubmitForm` runs `takePendingBlueprint()` on mount and feeds the file
  through the same `acceptFile()` validation + form-prefill path as a
  local drop.

The hand-off slot mirrors to sessionStorage as a base64 data string with
a 10-minute TTL, so the file survives Discord OAuth's full page round-
trip (`/api/auth/discord/login` → Discord → /api/auth/discord/callback
→ /submit). After login the in-memory copy is gone but the storage path
rehydrates a fresh File from the saved bytes. Storage is best-effort:
quota errors and private-browsing rejections are caught silently and the
in-memory path still works for already-logged-in users.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rebuilds the blueprint metadata entry screen from the Claude Design
hand-off (submit-blueprint.jsx). The old cream-on-yellow Tailwind
form is replaced by a full-bleed navy-paper layout that matches
/blueprints, with download stats and a live card preview front and
centre.

Hero
- Breadcrumb (`Blueprints / Submit`) + cyan eyebrow + "Submit a
  blueprint." headline + signed-in Discord pill (avatar / name /
  via-Discord caption / sign-out icon).
- 3-step stepper (Upload file / Add metadata / Review & PR); the
  first step ticks the moment a file lands, the third on success.

File area
- EmptyDrop: navy dropzone with cyan accent on drag, native file
  picker on click — same as the Blueprints upload tile so the
  hand-off is consistent.
- FileReceipt (post-parse): grid-paper card with a cyan check seal,
  filename, size pill, dimension chip, a 4-cell spec strip
  (Blocks / Entities / Biome / Sinking — sinking value highlights
  cyan when true), the derived slug, and a "Replace" button.
- Top blocks, top biome, entity total, and sinking flag are all
  derived client-side from the parsed JSON via summarizeBlueprint().

Metadata card
- GameModePicker as a 3-col tile grid (swatch + label + lowercase-
  mono sub-label). Pulls swatch from `catalog.gameModes[gm].color`
  when present, falls back to a hard-coded preset palette.
- Slug + Display name side-by-side, with live regex feedback under
  the slug.
- DescriptionField with a `n/10 lines` counter that shifts cyan
  near the limit and rose past it (the textarea border goes rose
  too — and the "≤ 10 lines" checklist item flips off).
- TagsField with real chips: Enter / comma to add, Backspace to
  remove last, × button per chip, max 6, autocomplete suggestion
  strip below; chip background pulled from `catalog.tags[t].color`.
- License `<select>` (EPL-2.0 / MIT / CC0 / CC-BY / CC-BY-SA) and
  Credit-as field (defaulted to the signed-in user) side-by-side.

Right rail (sticky, 380px)
- Live PreviewBlueprintCard mirrors the /blueprints gallery card
  pixel-for-pixel — the SVG VoxelStack rebuilds from the parsed
  topBlocks list and the variant is auto-picked from dimensions.
- 7-item readiness Checklist with `done/total` count.
- "What happens next" callout describing the PR / review flow.

Sticky bottom bar
- Mono filename + greyed `weblink/blueprints/<gm>/<slug>.blueprint`
  destination preview.
- Cyan "Open pull request" button (faGithub icon, disabled until
  every checklist item is satisfied).

Plumbing
- VoxelStack (and its block-colour palette + variantFor heuristic)
  is extracted from Blueprints.tsx into its own
  src/web/components/VoxelStack.tsx so Submit.tsx can render the
  live preview without pulling in the full Blueprints page bundle.
- /submit is pulled out of InteriorWrap so the navy hero can sit
  edge-to-edge under the sticky paper nav, and `body[data-route=
  "/submit"]` joins `/blueprints` in suppressing the legacy
  back.jpg cover.

Behaviour preserved verbatim
- GetMe auth gate; logged-out users see a navy login card linking
  to /api/auth/discord/login. The pendingBlueprint sessionStorage
  hand-off survives the OAuth full-page redirect, so a file dropped
  on /blueprints is still parsed and prefilled after sign-in.
- acceptFile() runs the same extension / 5MB / JSON / structure
  validation pipeline and prefills name / displayName / description
  the same way.
- PostSubmitBlueprint(csrfToken, fields, file) call signature is
  byte-identical; the new tag-array UI is joined to a
  comma-separated string at the wire boundary.
- Success state lands at the same prUrl from the server response;
  error state surfaces error.error / error.reason.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The runtime cron in api.ts ticks every 6 minutes and fetches at most
`env.github_downloads` addons per tick (default 1). On a fresh checkout
that means tens of minutes before /api/generate can return real ZIPs —
or hours if env.json still has the placeholder github_token, since
unauthenticated GitHub is capped at 60 requests/hour and the cron
hits the ceiling early.

The new script bypasses that drip-feed: instantiates ApiManager once,
fans out updateAsset() across every addon in config.json (concurrency
6) to populate the JarCache rows, then fans out updateJenkins() (4)
to fill the CI builds. Logs `✓` / `✗` per addon, exits when done so
the cron schedule from the constructor doesn't keep the process alive.

If env.json still has the `ghp_XXXX…` placeholder the script prints
an upfront warning explaining the rate-limit issue and how to set a
real PAT (no scopes needed for public repos).

Wired as `yarn prime-cache` in package.json (mirrors `yarn start`'s
`cd dist && node …` pattern, since both runtimes read config.json
and env.json relative to the dist working directory).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The live card preview was a 14-cube synthetic stack (island/tower/pad)
coloured by the most-common materials — same shape regardless of what
the user uploaded. Now it actually projects the parsed voxels.

Pipeline (computed once per file via useMemo, walked twice per render):

1. deriveVoxels(json.blocks) — flatten the [Vector, BlockData] pairs
   into {x, y, z, material}[]. Strips block-state suffixes
   ("[axis=y]"), drops air/cave_air/void_air, keeps water.
2. Build a Set<"x,y,z"> for O(1) neighbour lookup.
3. View-direction occlusion: our iso camera sits at +X +Y −Z and only
   ever sees the top (+Y), right (+X) and front-left (−Z) faces, so a
   voxel is invisible — and dropped — when all three of those
   neighbours are occupied. More aggressive than full surface culling
   since interior + back-side cubes both go.
4. Cube budget 2000: if still over, downsample by power-of-2
   floor-divide (each pass halves along each axis, 8× cell merge).
   Up to 16× max for very large blueprints.
5. Painter's-algorithm sort by (x+z) ascending, then y ascending.
6. Bounding-box-fit viewBox + SVG preserveAspectRatio="xMidYMid meet"
   so towers come out tall and wide pads come out wide instead of
   everything centering at the canvas size.

Palette + fallbacks:

- BLOCK_COLORS extended from ~30 to ~130 entries (full stone family,
  all 8 wood species + leaves, dirt/sand/snow/ice, ores + deepslate
  variants, lava/nether, end blocks, prismarine, mineral blocks,
  wool/concrete colour set, common decorative blocks).
- Suffix stripping handles slabs/stairs/walls/fences/doors/etc. by
  inheriting the base block's colour.
- Catch-alls for *_stained_glass, *_wool, *_concrete, *_terracotta.
- Anything still unmapped falls through to a hash-derived HSL triplet
  so unknown materials get a stable, visually distinct colour instead
  of collapsing to a grey default.

API:

- VoxelStack default export unchanged (still drives the synthetic
  preview on /blueprints gallery cards).
- New named exports: BlueprintVoxels component, deriveVoxels helper,
  RealVoxel type. Submit.tsx switches PreviewBlueprintCard to the new
  renderer when voxels.length > 0, falls back to VoxelStack when no
  file is dropped yet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
acceptFile() prefills name / displayName / description with
`setName(cur => cur || slug)` etc. — i.e. only when blank — so user
edits survive a re-derive. But Replace only nulled out `dropped` and
`dropError`, leaving those three fields populated from the previous
file. Dropping a second file then ran the prefill against non-empty
values and quietly skipped, so the form kept showing the old file's
metadata.

Reset name / displayName / description in clearFile() so the next drop
populates fresh. User-entered fields (gameMode, tags, license,
credit-as, terms checkbox) are intentionally preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three layers of plumbing for an in-app admin area, no editable UI yet:

- Schema + bootstrap: new isAdmin column on the users table, seeded from
  admin_discord_ids in env.json on startup; requireAdmin middleware;
  isAdmin surfaced in /api/me and on the frontend SessionUser.
- Persistence: ConfigStore layer with configOverrides + configAudits
  tables in Auth.sqlite. Overrides for 'addons' or 'presets' replace the
  matching slice of config.json at read time; reload() rebuilds the
  derived addons array, preserving live latest/beta versions from
  jarCache.
- /admin shell: lazy-loaded route gated by GetMe + isAdmin, with three
  stub tabs (Admins, Presets, Addons) to be filled in later stages.
  Conditional Admin nav link, return-path-aware login (?return=<path>)
  honored by the OAuth callback, and a Discord-blue user menu in the
  header (avatar + name -> Account / Admin / Log out).
GET/POST /api/admin/users (gated by requireAdmin + CSRF for writes).
Discord ID validated as 15-25 digits; self-demotion rejected server-side
even if the client somehow bypasses the disabled-button guard. Pre-grant
works: posting an unknown ID creates a stub user row that picks up its
profile fields on first Discord login.

UI lists admins with avatar / name / ID / last-login (or 'pending first
login' for stub rows), a Remove button per row (disabled for self), and
an Add-by-Discord-ID form below. Refreshes after every mutation.
GET /api/admin/presets returns the effective presets (base merged with
override). PUT replaces the whole array — array order is the displayed
order, and addons listed in any preset must match a known addon name.
Validation reports specific issues per index. On success, ConfigStore
records the override + audit row, then ApiManager.reload() rebuilds the
derived addons array so live latest/beta versions stay accurate.

UI is a working-copy editor: each preset is a collapsible card with
move-up / move-down / delete on the right, and an expanded inline form
with name, color picker (with hex input + live left-border swatch),
description, subtext, two chip pickers (addons, optional dependencies)
sorted with game modes first, and an optional dependencyText input.
Sticky save / discard footer. Validation issues from the server are
surfaced as a list above the footer.
GET /api/admin/addons returns the effective addons array. POST creates,
PUT /:name updates (the name field is locked because presets reference
addons by name), DELETE /:name removes — but only when no preset still
references it (returns the blocking preset list). BentoBox is protected
from deletion. Each mutation rewrites the full 'addons' override and
calls ApiManager.reload() so the derived this.addons (with live
latest/beta versions from jarCache) is rebuilt immediately.

UI lists addons with BentoBox pinned to the top under a 'Core plugin'
pill, then game modes, then addons (alphabetical inside each group).
Each row's editor covers the full AddonsEntity plus a compact versions
table for the per-Minecraft-version mappings — keys are validated as
[A-Za-z0-9._-]{1,32} so suffixed entries like '1.16-Java16' and any
future numbering scheme are accepted.
A new Recent tab is the default landing on /admin. Three sections:

1. Active overrides — lists each scope (presets, addons) currently
   diverging from config.json, with who edited it last and a Reset
   button that drops the override row and reloads.

2. Sync to git — Open PR button. Reads the effective config, renders it
   as 2-space JSON to match the existing config.json indentation, opens
   a branch on BentoBoxWorld/Downloads, commits the file with the
   override SHA, and opens a PR. PR body lists active scopes and the
   most recent audit entries. Disabled when there are no overrides;
   returns a clear error when admin_github isn't configured.

3. Activity log — last 50 audit-log entries (scope, summary, user, when)
   joined with the users table so they show display names rather than
   raw Discord IDs.

Wiring: env.json gains an admin_github block (owner / repo / branch /
token). When present, AdminManager constructs an Octokit. New routes
GET /api/admin/audits, GET /api/admin/overrides,
DELETE /api/admin/overrides/:scope, POST /api/admin/pr — all gated by
requireAdmin, with CSRF on the mutations.
Update Versions stat to "MC 1.15–26.1.x" and add 26.1 to the
Compatible-with badges now that Mojang has switched to year-based
major version numbers. Drop the Linux-only `unzip … -d plugins/`
snippet from the Quickstart card since it isn't cross-platform.
Switch the Footer byline to tastybento.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates accumulated work to introduce a Blueprints catalog (including Discord OAuth + submissions), add an admin interface for managing site config, and apply a broad visual refresh across the web UI.

Changes:

  • Added Blueprints catalog backed by a local BentoBoxWorld/weblink clone, plus download/zip endpoints and submission flow opening PRs to weblink.
  • Implemented an admin UI (Admins/Presets/Addons/Recent) and supporting API routes + persistence for overrides/audit logging.
  • Refreshed site styling (design tokens, new landing layout, updated Third-Party page, footer, and legal/account pages) and added a cache-priming script.

Reviewed changes

Copilot reviewed 41 out of 49 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/web/styles/design-tokens.css Adds shared CSS design tokens and UI utility classes for the refreshed look.
src/web/styles/GlobalStyles.tsx Imports the new design tokens into the app bootstrap.
src/web/pendingBlueprint.ts Adds client-side “pending blueprint” handoff via memory + sessionStorage.
src/web/index.html Updates theme color and loads Google Fonts used by the new design.
src/web/components/admin/RecentTab.tsx Adds admin “Recent” tab for overrides/audit log + export PR action.
src/web/components/admin/PresetsTab.tsx Adds admin presets reorder/edit UI and save/discard behavior.
src/web/components/admin/PresetEditor.tsx Adds preset editor UI (fields + addon/dependency picker).
src/web/components/admin/AdminsTab.tsx Adds admin grant/revoke UI by Discord ID.
src/web/components/admin/AdminPage.tsx Adds admin shell + tab navigation + auth gating.
src/web/components/admin/AddonsTab.tsx Adds admin addons CRUD UI wrapper and list presentation.
src/web/components/admin/AddonEditor.tsx Adds addon editor UI including per-Minecraft-version mappings.
src/web/components/ThirdParty.tsx Redesigns third-party catalog with hero, search, and tag filtering.
src/web/components/Terms.tsx Adds Terms of Submission page content.
src/web/components/Privacy.tsx Adds Privacy Policy page content.
src/web/components/PresetsPage.tsx Reworks presets UX and supports embedded rendering on Landing.
src/web/components/PremadeCard.tsx Redesigns preset cards and actions (download/customize).
src/web/components/Landing.tsx Introduces new landing page layout and embedded presets section.
src/web/components/Footer.tsx Adds a new footer with site navigation + legal links.
src/web/components/ContentBox.tsx Restructures routing (landing, blueprints, submit, admin, legal, etc.) and adds route-aware body styling.
src/web/components/Account.tsx Adds account page (logout, submissions list, delete account).
src/web/ApiRequestManager.ts Adds API helpers for blueprints, auth/account, and admin actions.
src/scripts/prime-cache.ts Adds a script to prime JarCache in one run with concurrency control.
src/index.ts Adds CSP via helmet, cookie parsing, auth/admin/submission routes, and image serving for weblink images.
src/config.d.ts Adds TypeScript types for blueprint catalog structures.
src/api/weblink.ts Implements shallow clone/pull sync of the weblink repo.
src/api/submissions.ts Implements blueprint submission validation, rate limiting, and PR creation to weblink.
src/api/models/configStore.ts Adds Sequelize models for config overrides and audit log.
src/api/models/auth.ts Adds Sequelize auth models (users, sessions, submissions).
src/api/configStore.ts Adds persistent override store and audit logging for config scopes.
src/api/blueprintCatalog.ts Builds blueprint catalog from repo contents + derives stats and resolves safe paths.
src/api/auth.ts Implements Discord OAuth, sessions, CSRF, rate limiting, and account deletion.
src/api/api.ts Integrates weblink sync, blueprint catalog refresh, auth/admin/config store, and new endpoints.
src/api/admin.ts Implements admin APIs for admins/presets/addons/audit/overrides and export PR.
package.json Adds scripts and new dependencies for auth/submissions/admin features.
env.example.json Documents new env configuration blocks for weblink/auth/admin PR export.
CLAUDE.md Updates repo documentation for new pages/features and operational behavior.
.gitignore Ignores runtime data/, sqlite DBs, and design handoff bundles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/index.ts Outdated
// Auth: load session for every /api request, then expose discrete routes.
const wrap = (fn: (...a: unknown[]) => unknown) =>
(req: express.Request, res: express.Response, next: express.NextFunction) =>
fn(req, res, next);

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap() invokes handlers without wiring async errors into Express. If any wrapped handler returns a rejected Promise (many are async), the rejection won’t be forwarded to next() and can become an unhandled rejection. Consider implementing wrap as Promise.resolve(fn(req,res,next)).catch(next) (or returning the promise) so errors propagate correctly.

Suggested change
fn(req, res, next);
Promise.resolve()
.then(() => fn(req, res, next))
.catch(next);

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +30
title: 'Downloads',
links: [
{ label: 'Presets', to: '/presets', icon: faCube },
{ label: 'Custom build', to: '/custom', icon: faWrench },
{ label: 'Third-party addons', to: '/thirdparty', icon: faPuzzlePiece },
{ label: 'Blueprints', to: '/blueprints', icon: faLayerGroup },

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This footer link points to /presets, but the router no longer defines a /presets route (presets now live on / under #presets-anchor). This will lead users to a 404. Update the link target (e.g. /#presets-anchor) or restore a /presets route that redirects to the landing section.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +89
{addon.AuthorLink ? (
<a
href={addon.AuthorLink}
target="noopener"
style={{
color: 'var(--bb-ink)',
textDecoration: 'underline',
textDecorationColor: 'var(--bb-paper-edge)',
}}
>

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These external links use target="noopener", which is not the same as opening a new tab/window (target="_blank") and can reuse an existing named browsing context. For external links, use target="_blank" together with rel="noopener noreferrer" to avoid tab-nabbing and ensure consistent behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +130
{addon.Releases && (
<a
href={addon.Releases}
target="noopener"
className="bb-btn bb-btn-primary"
style={{

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: target="noopener" should be target="_blank" and paired with rel="noopener noreferrer". As written, the link may not open in a new tab and lacks the standard rel attributes for external navigation.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +23
<a
href="https://github.com/BentoBoxWorld/weblink"
target="noopener"
css={tw`text-blue-700 underline`}
>
BentoBoxWorld/weblink
</a>

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This GitHub link uses target="noopener" and is missing rel="noopener noreferrer". Use target="_blank" + rel="noopener noreferrer" for external links.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
sublicense the Content under the terms of the&nbsp;
<a href="https://www.eclipse.org/legal/epl-2.0/" target="noopener" css={tw`text-blue-700 underline`}>
Eclipse Public License 2.0
</a>
&nbsp;(or any later version of that license at our discretion).

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External links here use target="noopener" without a rel. target should be _blank for new-tab behavior, and rel="noopener noreferrer" should be added for security.

Copilot uses AI. Check for mistakes.
Comment thread src/web/components/Privacy.tsx Outdated
Comment on lines +49 to +58
async function handleCreate(addon: AddonsEntity) {
await PostAdminAddon(user.csrfToken, addon);
setCreating(false);
load();
}
async function handleUpdate(name: string, addon: AddonsEntity) {
await PutAdminAddon(user.csrfToken, name, addon);
setExpanded(null);
load();
}

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleCreate / handleUpdate don’t catch request failures. If the API returns an error (validation, auth, network), the Promise rejection will surface to the console and the UI won’t display an error state or keep the editor open. Wrap these in try/catch and set an error message (and only close the editor on success).

Copilot uses AI. Check for mistakes.
Comment thread CLAUDE.md Outdated
Comment thread src/index.ts Outdated
tastybento and others added 4 commits April 26, 2026 13:37
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- src/index.ts: wrap async route handlers with Promise.resolve().catch(next)
  so async errors propagate to Express's error handler instead of becoming
  unhandled rejections.
- Footer.tsx: point Presets nav link at /#presets-anchor (the /presets
  route was removed; the anchor now lives in the Landing page) so the
  link doesn't fall through to the 404 catch-all.
- ThirdParty.tsx, Privacy.tsx, Terms.tsx: replace target="noopener" with
  the standard target="_blank" rel="noopener noreferrer" combo on all
  external links.
- CLAUDE.md: drop the stale "signed via cookie-parser" wording — the
  session cookie carries an opaque server-generated id, not a signed
  payload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tastybento

Copy link
Copy Markdown
Member Author

Thanks for the review! Pushed db19ffa addressing the four buckets I agreed with:

  • wrap() async errors (src/index.ts) — applied the suggested Promise.resolve().then().catch(next) so route handler rejections reach Express.
  • Broken /presets link (Footer.tsx) — pointed it at /#presets-anchor so it lands on the new presets section in Landing rather than the 404 catch-all.
  • target="noopener" — fixed in ThirdParty.tsx (5 places), Privacy.tsx, and Terms.tsx (2 places). All now use target="_blank" + rel="noopener noreferrer".
  • Stale "signed via cookie-parser" wording (CLAUDE.md) — removed; the cookie carries an opaque server-generated id, mirroring the Privacy.tsx wording fix already accepted.

Pushing back on three:

  • PresetsTab.tsx array index as React key — the only index-tracked state outside draft is expanded, and every reorder/insert/delete handler explicitly remaps it (setExpanded(target), setExpanded(expanded - 1), etc.). The row body has no internal state, so an index-key reorder doesn't actually shuffle anything stateful here. Switching to stable IDs is a worthwhile follow-up but a separate refactor.
  • ContentBox.tsx:117 "comments mention /presets" — the flagged lines are a code comment explaining why /presets isn't routed (the section now lives inside Landing under #presets-anchor). The actual user-visible 404 was the Footer link, fixed above.
  • AddonsTab.tsx handleCreate / handleUpdate no try/catchhandleDelete already has structured handling for the cases that produce user-actionable messages (addon_in_use, protected_addon); a generic alert(err) on create/update wouldn't add information beyond what already shows in the dev console. This is an admin-only tool, and CLAUDE.md guidance leans against speculative error handling. Happy to revisit if a specific failure mode comes up.

@tastybento
tastybento merged commit 37dc1a6 into develop Apr 26, 2026
2 checks passed
@tastybento
tastybento deleted the feature/admin-interface-and-site-refresh branch April 26, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants