Repository navigation
feat: free Magic Mark rebuilt on the Strapi Design System - #2
Merged
Merged
Conversation
Imports the uncommitted Codex work as the overhaul base: Content Manager filter depth guard, filterable attribute allowlist, percent-safe query round trip, malformed filter parsing, dropdown positioning and log redaction, with regression tests (39/39 pass, both type checks clean). The release workflow change that removed the NPM_TOKEN fallback is not imported; the fallback stays until npm trusted publishing is confirmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- @strapi/strapi, @strapi/utils and @strapi/typescript-utils 5.61.0, qs 6.17.1, zod 4.6.7 (request validation), semantic-release plugins and Prettier latest. - Runtime dependencies are qs and zod only. Design System, icons, React, React Router, styled-components, react-intl and @strapi/utils are peers supplied by the host; @types/qs moved to devDependencies. - Removed: @heroicons/react (replaced by @strapi/icons), @swc/core (unused), the direct vite pin (@strapi/sdk-plugin 6.1.1 brings vite ^6.4.2) and @strapi/sdk-plugin as a peer (it is a build tool). - Pins kept on purpose: React 18, React Router 6, styled-components 6 and react-intl 6 are the Strapi 5.61 admin peers; TypeScript stays on 5.9 because @strapi/sdk-plugin 6.1.1 ships 5.9.3 and typescript-eslint 8.72 supports TypeScript < 6.1. - ESLint 10 flat config with typescript-eslint, react-hooks and eslint-config-prettier; lint, lint:fix, format and format:check scripts. - strapi-server.js and strapi-admin.js load dist (the old file required a TypeScript source that is not published); verify:runtime checks the built server bundle; engines, publishConfig and files updated. - Strict TypeScript in admin and server. Docs: npm view (2026-11-07), typescript-eslint.io flat config, ESLint 10 flat config docs, @strapi/sdk-plugin 6.1.1 package.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Free: - Remove the license guard, license controller and routes, license-check policy and middleware, the 10/50 bookmark limit and every call to the license server. No network calls, no timers, no device fingerprinting. - Bootstrap deletes the stored licenseKey and lastValidated keys once. - Remove COPYRIGHT_NOTICE.txt (it forbade removing the license system; the plugin is MIT) and the ad-hoc test-magic-mark.js script. Security and function: - Every admin route uses admin::isAuthenticatedAdmin and admin::hasPermissions; auth.scope was ignored by the admin strategy, so any admin could call every route (MM-F01, SEC-01). - New actions bookmarks.read/create/update/delete/share/manage. Roles that held the 3.x settings.read/settings.update actions get the matching new actions in the afterSync hook, before the admin removes unknown actions. - zod validation for every body and query (strict, bounded); list view paths must be Content Manager list views and queries are normalised with qs (no page, no prototype keys, no backslash or fragment) (SEC-02/03/06). - Sharing moves to bookmark-share rows keyed by documentId, so sharing with users works (MM-F02) and access is filtered in SQL with pagination, search, scope and content type filters; owner names load in one query (MM-F05, SEC-07). Recipients never see sharing lists. - Pins are per user (bookmark-preference rows) and work on shared bookmarks (MM-F12). Duplicate, update-with-current-view endpoints. - Not visible -> 404, visible but foreign -> 403; bookmarks.manage edits and deletes any visible bookmark (MM-F13). - User search is paginated and returns email only with admin::users.read (MM-F14, SEC-04). - Multi-instance safe: 3.x rows (relative paths, share JSON with numeric or documentId ids, global pins) are migrated with guarded updateMany claims and transactions; deleting admin users or roles cleans up shares, pins and private bookmarks through db lifecycles (unsubscribed in destroy). - Errors use @strapi/utils classes; logs carry no keys or personal data. Tests: server-service, server-validation, server-routes (real hasPermissions policy), server-controller, server-lifecycle. Docs: docs.strapi.io server-routes, server-policies-middlewares, admin-permissions-for-plugins, document-service, error-handling; installed @strapi/admin 5.61.0 strategies/admin.js, policies/hasPermissions.js, bootstrap.js (cleanPermissionsInDatabase), @strapi/core Strapi.js (afterSync hook order), @strapi/database transaction-context and lifecycles; zod 4 flattenError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every element is a @strapi/design-system 2.2.4 component or the @strapi/strapi/admin page API; no custom styled components, colour literals or browser dialogs remain (about 9,000 lines of custom UI, license screens and dead modules removed). Content Manager list view (one injection, two icon buttons next to the native View settings button): - Advanced filters: DS Modal with one match choice (all/any) and a flat list of conditions (field Combobox, native operator wording, typed value inputs). "All" writes the native $and shape, so the conditions show as native filter tags; applying uses useQueryParams without a page reload (MM-F06, MM-F07, UX-03..UX-11). Fields come from /content-manager/init and the list configuration, so editors need no Content-Type Builder access, including relation main fields and component fields (MM-F04, MM-F15). - Bookmarks: DS Popover with the bookmarks of this content type, pinned first, Save view, pin, update with current view and Manage bookmarks. Opening a bookmark replaces the Content Manager's remembered list settings for that type, so stale filters do not mix in (MM-F09); paths are router paths, so a custom admin URL works (MM-F08). Bookmarks page: Page/Layouts header, SearchInput, scope and content type selects, DS Table (cards below 768 px), admin Pagination, row pin and a More menu (open, edit, duplicate, copy link, delete with ConfirmDialog), EmptyStateLayout states, Page.Protect. Create/edit in a DS Modal with a read-only summary of the saved view and Only me / roles and users / all admins sharing with server-side user search. Homepage widget "Pinned bookmarks" (feature-detected). No settings section, no license pages, no keep-alive polling, no console output. All strings in en, de, fr, es and pt; German without forms of address. Tests: admin-filters (qs depth-5 round trips, native tag removal, prototype pollution), admin-listview, admin-i18n (key parity, ICU, placeholders, no Du/Sie, no emoji), source-contract. Docs: installed @strapi/admin 5.61.0 Filters.mjs, useQueryParams.mjs, WidgetHelpers, Widgets API, Pagination; @strapi/content-manager 5.61.0 ListViewPage.mjs, usePersistentQueryParams.mjs, InjectionZone.mjs, routes/admin.js; @strapi/design-system 2.2.4 types (Modal, Popover, Combobox, MultiSelect, Radio, Table, Tag); docs.strapi.io admin-injection-zones, homepage widgets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test.yml: Node 22 and 24, lint, format check, tests, blocking type checks, build, verify and verify:runtime; read-only token, concurrency, timeout, SHA-pinned actions, no persisted credentials (CI-1). - release.yml: same checks plus a production audit before semantic-release; npm trusted publishing with the NPM_TOKEN fallback kept, provenance on, installs without lifecycle scripts, npx --no-install (REL-1). The .releaserc keeps failCommentCondition: false. - pages.yml: SHA-pinned actions and job-scoped Pages permissions. - Dependabot: weekly npm and GitHub Actions updates; Strapi peer majors and TypeScript majors are ignored (documented pins). Patch and minor updates are merged after the full verification (REL-2). - Issue and pull request templates formatted with Prettier. - tests/package.test.cjs checks dependencies, files, scripts, workflows. Docs: GitHub Actions security hardening, Dependabot options reference, semantic-release npm plugin (trusted publishing, provenance). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…all screens
- The admin router adds extra keys ("0") to ctx.params; the strict id
schema rejected them, so GET /bookmarks/:id answered 400. Controllers now
validate { id } only (found on the isolated 5.61 host; regression test).
- The bookmarks popover is limited to the available height and scrolls,
so "Save view" stays reachable at 320 px; the pin button keeps its
position whether or not "update with current view" is shown.
- Bookmark names in the table use the Design System Link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README follows the workbench standard: screenshots (desktop, dark, 390 px), core concepts, screens, compatibility with the documented peer and TypeScript pins, install, permission and route tables, security and privacy, troubleshooting, upgrade notes from 3.x, development and tests. - GitHub Pages site documents the free plugin, permissions and routes with screenshots; the license and upgrade content is gone (FREE-7, DOC-1). - New synthetic screenshots from the isolated Strapi 5.61 host replace the 3.x images. - CHANGELOG [Unreleased] with migration notes. - Repository maintenance skill describes the free model and the gates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm audit fix (without --force) updates transitive dev dependencies: 88 -> 83 advisories, 0 critical, 0 in production dependencies (npm audit --omit=dev: 0). The remaining highs sit inside @strapi/strapi 5.61.0 (its pinned zod, koa-session, koa-body and @strapi/* packages); npm's only offered fix is a downgrade to Strapi 4, so they stay until Strapi ships updates. Lint, tests, type checks, build and verify pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ters /content-manager/init lists id and documentId for components. Components have no documentId column, so a condition on it made the Content Manager list request fail with a 500. Both internal keys are now skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… after delete - Closing the create/edit dialog with unsaved changes (Escape, Close, Cancel) now opens a ConfirmDialog, like the native Content Manager Blocker; Cancel keeps the edits. A pristine form still closes directly. New message form.discardBody in en, de, fr, es and pt. - After deleting a bookmark, focus moves to the page content (the Page.Main skip target) instead of falling back to <body> when the row disappears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Design System Badge primary variant (primary700 on primary200) measures 2.4:1 in the dark theme. Visibility badges now pass explicit token pairs that measure at least 4.9:1 in both themes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 768 px the six-column table scrolled horizontally and hid the row menu (the only way to edit or delete). Between the medium and large breakpoints the Filters column is now dropped, so the table fits; desktop keeps all columns, mobile keeps cards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… dialogs - The pin toggle and the More actions menu now sit in separate table cells. A Design System table cell with more than one button calls preventDefault on every key event, including the Escape that bubbles through React from the portalled menu, so the menu stayed open. The menu is controlled like the native Content Manager row menu. - Edit and delete dialogs opened from a row menu return focus to that row's More actions button when they close; after a delete, focus moves to the page content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Menu.Content defaults to a 15rem max height, so near the bottom of the viewport the five actions scrolled and Open or Delete were hidden. The menu now has no max height, like the native Content Manager row menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With separate pin and menu cells the table still overflowed by a few pixels at 768 px, hiding part of the row menu. Tablets now use the card layout that phones already used; the full table starts at the Design System large breakpoint (1080 px). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trigger tooltip stayed open while the modal menu was open and covered the last item (Delete) when the menu flipped above the button near the bottom of the viewport. The trigger keeps its label as the accessible name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synthetic data from the isolated 5.61 host, taken after the keyboard, contrast and tablet fixes (opened and checked: example.test users only, no tokens or paths). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… saving - Holders of bookmarks.manage now reach every bookmark that is not private: public ones, ones shared with anyone and ones without an owner, so shared bookmarks of deleted users can be removed. They are listed with the new 'managed' scope (Show > All shared bookmarks); private bookmarks of other users stay hidden. The permission is renamed to 'Manage shared bookmarks of others' (MM-RA-1). - 3.x queries are normalised during the migration (page and unknown keys dropped) and duplicate never copies an unnormalised query (MM-RA-2). - A search text of up to 1000 characters can be saved; when the server still rejects a view, the dialog and the update action say why (MM-RA-3). - The Bookmarks popover requests pinned bookmarks separately and links to the bookmarks page when a content type has more than 100 (MM-RA-4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ange The README and site already said 5.57 or newer; the peer range said ^5.0.0. Both @strapi/strapi and @strapi/utils peers are now ^5.57.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…E and site README: hero screenshot, feature list, one-minute quick start, examples, performance and multi-instance notes, FAQ and contributing guide. Site: dark mode via prefers-color-scheme, Open Graph and canonical tags, FAQ and multi-instance sections. Checked at 1440, 768, 390 and 320 px in light and dark: no overflow, no broken anchors or images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Magic Mark is now free and rebuilt on the Strapi Design System. This branch removes the license system, secures every admin route on the server and redesigns the admin UI with Design System components only.
licenseKeyandlastValidatedvalues are deleted once on bootstrap. There is no telemetry and the plugin makes no outbound requests.admin::isAuthenticatedAdminplusadmin::hasPermissions, and owners are checked in the service. Bookmarks you cannot see answer 404, foreign ones you can see answer 403. Requests are validated with strict zod schemas, and paths and queries are normalised withqs.Re-attack fixes in this round
bookmarks.managenow reaches every bookmark that is not private: public ones, ones shared with anyone (also when the manager is not a recipient) and ones without an owner. Managers list them under Show > All shared bookmarks (scope=managed, 403 without manage). Private bookmarks of other users stay hidden. The permission is now called "Manage shared bookmarks of others".pageand unknown keys are dropped.duplicatenever copies an unnormalised query._qcan now be up to 1000 characters. A rejected query shows a translated Alert in the save dialog and a matching notification for "update with current view".Proof
lint,format:check,test(75/75),test:ts:front,test:ts:back,build,verify,verify:runtimeall pass. The new tests fail without the fix (4 failures on the old code)._qof 201 characters saves and 1001 answers 400 withfieldErrors.query; a pin on the 106th bookmark comes back from the pinned request.npm pack: 26 files, 101.3 kB, onlydist, entry files, README, CHANGELOG and LICENSE.npm audit --omit=dev: 0 vulnerabilities.Release impact
There is no
BREAKING CHANGEfooter, because 3.x permissions and data migrate automatically. semantic-release will therefore publish a minor version. Plugin-internal admin routes were removed (/license/*,/roles,/users,/bookmarks/reorder), and so were manual ordering and the 3.x action ids. The peer range for@strapi/strapiand@strapi/utilsis now^5.57.0, which matches the README. The owner should decide whether a major version fits better.Open points
@strapi/strapi5.61.0. Every fix npm offers is a downgrade. TypeScript stays on 5.9 because@strapi/sdk-plugin6.1.1 and typescript-eslint 8 require it.creatorId(MM-F17), which a content type schema cannot declare.NPM_TOKENfallback.mainpublishes to npm.🤖 Generated with Claude Code