Admin interface, Blueprints catalog, and site visual refresh#17
Conversation
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>
There was a problem hiding this comment.
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/weblinkclone, plus download/zip endpoints and submission flow opening PRs toweblink. - 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.
| // 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); |
There was a problem hiding this comment.
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.
| fn(req, res, next); | |
| Promise.resolve() | |
| .then(() => fn(req, res, next)) | |
| .catch(next); |
| 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 }, |
There was a problem hiding this comment.
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.
| {addon.AuthorLink ? ( | ||
| <a | ||
| href={addon.AuthorLink} | ||
| target="noopener" | ||
| style={{ | ||
| color: 'var(--bb-ink)', | ||
| textDecoration: 'underline', | ||
| textDecorationColor: 'var(--bb-paper-edge)', | ||
| }} | ||
| > |
There was a problem hiding this comment.
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.
| {addon.Releases && ( | ||
| <a | ||
| href={addon.Releases} | ||
| target="noopener" | ||
| className="bb-btn bb-btn-primary" | ||
| style={{ |
There was a problem hiding this comment.
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.
| <a | ||
| href="https://github.com/BentoBoxWorld/weblink" | ||
| target="noopener" | ||
| css={tw`text-blue-700 underline`} | ||
| > | ||
| BentoBoxWorld/weblink | ||
| </a> |
There was a problem hiding this comment.
This GitHub link uses target="noopener" and is missing rel="noopener noreferrer". Use target="_blank" + rel="noopener noreferrer" for external links.
| sublicense the Content under the terms of the | ||
| <a href="https://www.eclipse.org/legal/epl-2.0/" target="noopener" css={tw`text-blue-700 underline`}> | ||
| Eclipse Public License 2.0 | ||
| </a> | ||
| (or any later version of that license at our discretion). |
There was a problem hiding this comment.
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.
| 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(); | ||
| } |
There was a problem hiding this comment.
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).
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>
|
Thanks for the review! Pushed
Pushing back on three:
|
Summary
Bundles 16 commits of accumulated work on
developinto a single PR for review. The changes group into four themes:Blueprints catalog (new feature)
BentoBoxWorld/weblinkrepo, refreshed on the same 6-min cron used for addon pollingidentifyscope) + signed-session cookies + CSRF + rate-limited blueprint submissions that open PRs againstweblink/submitpage redesigned in the navy-paper sub-brand, live preview that renders the actual blueprint, file hand-off from the Blueprints upload tileAdmin interface
Site visual refresh
MC 1.15–26.1.x,26.1added to the Compatible-with badges)unzip … -d plugins/snippet from the Quickstart cardMisc
yarn prime-cachescript to seedJarCache.sqlitein one shot.gitignorefor macOS metadata, runtimedata/directory, and design bundlesTest plan
yarn buildsucceedsyarn siteboots; landing renders with the new Compatible-with badges (26.1 first) and nounzipsnippet/blueprintslists catalog entries fromdata/weblink/; downloading a single blueprint and a multi-select zip both work/submit; submission opens a PR againstBentoBoxWorld/weblink🤖 Generated with Claude Code