@emdash-cms/plugin-cli@0.8.1
49 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
582823328e |
fix: preserve Unicode in routable content slugs (#2505)
* fix: keep routable content addressable across languages * fix: keep invalid legacy slugs out of sitemaps * fix: close routable publish bypasses * fix: support slugless non-routable seeds * fix: protect published routable slugs on update * fix: normalize unusable slugs in seed exports * fix: reject slugless routable schedules * chore: clarify slug generation contract * chore: remove editor implementation narrative * test: keep revisionless fixture routable |
||
|
|
9b998224a5 |
fix(editor): protect inline edits from file navigation (#2502)
* fix(editor): protect inline edits from file navigation * test(editor): rely on cancellable transfer behavior * fix(editor): own unsupported file drag lifecycle * fix(editor): reannounce repeated file guidance |
||
|
|
1c4d4f04b2 |
feat(core): expose outer user middleware hook (#2499)
* feat(core): wrap EmDash with user middleware * fix(core): validate outer middleware config |
||
|
|
2b3741e3e0 |
test(e2e): give fixture post pages a head so component styles ship (#2415)
* test(e2e): give fixture post pages a head so component styles ship The fixture's post page opens `<html>` and goes straight to `<body>`. Astro has no head to inject into, so the page ships no styles at all: every scoped style of the components it renders — `Comments`, `CommentForm` and the whole Portable Text component tree — is dropped. Measured on the rendered page: zero `<style>` tags and zero stylesheet links, while the elements still carry their classes and Astro's scoping attribute. That leaves public rendering untestable end to end. Components ship as source and are compiled by the consuming site, so "the styles reach the page" is a guarantee worth a test; the specs that already load this page assert text and attributes only, and pass either way. Add the head, and a spec that reads a component's applied style from the browser so a regression in style delivery fails here instead of shipping. * test(e2e): retry the cold public route before asserting styles The spec navigated straight to the post page and asserted a computed style. The workerd dev runner's Vite dep optimizer can transiently 500 a cold route even after the global setup's warm-up, which covers the admin and content API routes but not this public page. Playwright's assertion retry does not help there: once the navigation lands on an error page the element never appears, so the page has to be reloaded. Wrap the navigation in the same reload loop the sibling specs use. --------- Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
a15a226189 |
test(e2e): wait for dashboard metrics before asserting layout (#2434)
The two dashboard layout tests read `dashboard-metric` cards immediately after `waitForLoading()`, which only waits on the text "Loading" and `.animate-spin`. The dashboard renders Kumo `SkeletonLine` placeholders while the stats query is in flight, so neither selector matches and the wait returns straight away. `expect(await count())` takes a single snapshot with no retry, so a slow stats response fails the run -- observed on the Cloudflare E2E shard as "Expected: >= 3, Received: 0" with the page snapshot still showing the skeleton state. Use `expect.poll` for the card count and a web-first visibility assertion before the tracking test's `evaluate`, both of which retry. Verified by delaying the dashboard stats response 3s locally: both tests fail without these changes and pass with them. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5a5adb79d2 |
fix(core): make editor text alignment render on public pages (#2410)
* fix(core): make editor text alignment render on public pages * test(core): prove alignment CSS reaches a rendered page The repro test asserted Astro's `data-astro-cid-*` scoping attribute as a stand-in for "the alignment rules ship and apply". That proxy cannot fail on the regression it guards: the attribute is stamped whenever the component carries any scoped style, so deleting or mistyping the rules keeps it green. The container API cannot do better — `renderToString` emits markup only, and the compiled style module resolves to an empty string under the repro config. Move the CSS claim to the level that can see it. A fixture page renders a Portable Text value through the public component tree, and an e2e spec reads the computed alignment from the browser. The repro test keeps the part it can prove on its own: the class the Block override emits per `textAlign`. The fixture page carries its own markup instead of a seeded entry so the suite's shared content stays untouched, which also keeps the admin visual-regression baselines unchanged. Drop the comments flagged in review and lower the changeset to the effect a reader upgrading the package can observe. |
||
|
|
08c8f2b68c |
refine(admin): unify settings pages (#2341)
* refine(admin): establish settings layout and refine settings hub * refine(admin): migrate general settings to shared layout * refine(admin): migrate social settings to shared layout * refine(admin): migrate seo settings to shared layout * refine(admin): migrate security settings to shared layout * refine(admin): migrate allowed domains settings to shared layout * refine(admin): migrate api token settings to shared layout * Revert "refine(admin): migrate api token settings to shared layout" This reverts commit d7ca580183d6347f6588e3eac891079f853ec554. * Revert "refine(admin): migrate allowed domains settings to shared layout" This reverts commit ef6a153b0716028d5542b62c5943c6cad59a89b9. * Revert "refine(admin): migrate security settings to shared layout" This reverts commit 6319b92e5a63d378e2191f361475793f86a837d5. * Revert "refine(admin): migrate seo settings to shared layout" This reverts commit cf06366adda1d4f9418ebc97a4520bdbcba235b6. * Revert "refine(admin): migrate social settings to shared layout" This reverts commit 5d868f08b4a795a35fa64274a8a25fa7099ed9d3. * Revert "refine(admin): migrate general settings to shared layout" This reverts commit e3121dab81a2f23e51a00c86f87295ecfa52335c. * Revert "refine(admin): establish settings layout and refine settings hub" This reverts commit e496f212384ed40f90aa35f3e66faedcad79b497. * refine(admin): establish settings layout and refine settings hub * refine(admin): improve settings hub typography and copy * refine(admin): migrate general settings to shared layout * refine(admin): migrate social and seo settings to shared layout * refine(admin): migrate security and signup domain settings to shared layout * refine(admin): match security empty state border * refine(admin): strengthen settings empty state borders * refine(admin): clarify settings empty state borders * refine(admin): migrate api token settings to shared layout * refine(admin): migrate email settings to shared layout * refine(admin): migrate backup settings to shared layout * refine(admin): polish api token settings layout * refine(admin): polish backup settings actions * refine(admin): polish settings localization and controls * fix(admin): harden settings page interactions * refine(admin): align general settings media rows * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(admin): clarify settings language section * fix(admin): restore settings accessibility and e2e coverage * fix(admin): localize passkey activity timestamps --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
d2c259cb7f |
fix(admin): normalize content lifecycle status badges (#2340)
* refine(admin): normalize publish labels * refine(admin): normalize lifecycle status badges * fix(admin): normalize non-draft status badges * chore(admin): satisfy type-aware status lint * style: format * test(admin): update normalized publish expectations --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com> |
||
|
|
1d7c06395b |
fix(admin): refine the welcome dialog layout (#2263)
* fix(admin): refine welcome dialog hierarchy The first-login dialog led with a 64px logo and a 24px heading while its only action was a default secondary button, and the role sat in a tinted card with the label rendered in brand blue -- reading as a link. Copy was split across four alignment axes. Left-align the content, move a 32px logo into the header row opposite the close button, replace the tinted card with hairline dividers, show the role as a Badge, and promote the action to a primary button. Badge emphasis now tracks permission reach; `info` is avoided because it measures 3.6:1 against the dialog surface, under WCAG AA for 12px text. Type follows the scale established in #2252 (`leading-tight` headings, `text-sm leading-5 text-pretty` body). The previous `leading-none` title collided with the description once a long name wrapped. * fix(admin): stretch the welcome dialog action to full width The dialog has a single action, so a right-aligned button left the footer lopsided against full-width content above it. * fix(admin): even out the welcome dialog footer The first divider led with 20px and the second with 16px, and the full-width action sat at 400x36, reading thin against its own width. * chore(admin): mention the full-width action in the changeset * chore(admin): drop the role badge justification comment * feat(admin): add a role icon to the welcome dialog badge * refactor(admin): use the current UserCircleIcon export UserCircle is deprecated in favour of UserCircleIcon. * test(admin): cover wrapped welcome title spacing * Update e2e/tests/welcome-modal.spec.ts Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com> --------- Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com> |
||
|
|
6a0e93a692 |
fix(admin): adopt Kumo's brand colours (#2262)
* fix(admin): adopt Kumo's brand blue The classic theme pinned --color-kumo-brand to the WordPress admin blue (#2271b1), roughly half the chroma of Kumo's own brand, so primary buttons rendered muted. Drop the brand, brand-hover and link overrides so they inherit Kumo and track future updates. --color-kumo-ring goes too: nothing in the admin or Kumo ever read it. Brand text keeps an override. Kumo maps --text-color-kumo-brand to the Cloudflare orange wordmark (#f6821f), which would have recoloured 62 text-kumo-brand sites -- sign-in links, verified badges, and links inside user content -- to orange at roughly 2.6:1 on white. Pointing it at the link colour keeps those blue at 8.5:1 light and 7.7:1 dark, up from 5.2:1. * fix(admin): drop the brand text override Brand text resolves to Kumo's value alongside the fill, so no brand token stays overridden in the classic theme. Accents using text-kumo-brand now render Kumo's orange rather than a blue. * fix(admin): colour links with Kumo's link token Kumo maps --text-color-kumo-brand to the Cloudflare orange wordmark. The admin used text-kumo-brand for links and accent icons, so inheriting Kumo's value rendered them orange at 2.5:1 against the light surface, under the 4.5:1 needed for text and the 3:1 needed for meaningful icons. Point all 62 call sites at text-kumo-link, the token that already means interactive text. Measures 8.8:1, and no theme token stays overridden. Border and background brand utilities are untouched; they read --color-kumo-brand, which is blue in both themes. * test(e2e): follow the renamed locale marker class The current-locale marker moved from text-kumo-brand to text-kumo-link, so the selector no longer matched. |
||
|
|
f81f72000b |
fix(admin): refine typography hierarchy (#2252)
* feat(admin): refine typography hierarchy * docs(admin): remove typography guide |
||
|
|
6379873dbb |
fix(e2e): make visual-regression editor captures deterministic (#2192)
Blur the active element before capturing so a hydration focus race on the TipTap toolbar cannot leave a button highlighted in some runs and not others, which produced spurious diffs on the content-editor and content-new screens. Also decide visual drift from git status of the snapshots directory rather than from a non-zero Playwright exit. A flaky render can fail the first-pass diff yet regenerate byte-identical baselines under --update-snapshots; the old candidate-emptiness check always saw the committed baselines and flagged drift regardless. Now a clean tree after regeneration is treated as flake or infra failure and keeps the check green with a warning. |
||
|
|
73dadc2756 |
test: add initial visual regression baselines (#2165)
* test: add initial visual regression baselines * docs: update visual test gating comment |
||
|
|
8ed0a9b1c5 |
test: add visual regression suite for the admin UI (#2147)
* test: add visual regression suite for the admin UI
Adds a Playwright visual-regression suite covering key admin screens in
LTR (English) and RTL (Arabic), gated behind EMDASH_VISUAL=1. Adds the CI
trio: an untrusted measure job, a trusted report job that comments diffs,
and a reaction-gated apply job that commits accepted Linux baselines on a
maintainer 👍. Adds inert data-testid hooks to mask drift-prone timestamps
and version strings.
* fix: address review on visual regression suite
- Widen visual.yml path filter to the whole e2e tree + playwright.config
so fixture/seed/setup changes that alter screenshots trigger a measure run
- Await document.fonts.ready without returning the non-serializable
FontFaceSet, which would throw in Playwright
- Import the canonical ServerInfo type from the fixtures instead of
redeclaring a narrowed copy
- Report workflow: on a bootstrap run with no prior baselines, show the
proposed candidate baselines instead of an empty "0 screens changed" table
- Add an empty changeset (only published-package change is inert testids)
* ci: run visual regression on lockfile/manifest changes
A Playwright or Chromium version bump alters pixel output, so a
pnpm-lock.yaml or package.json change can drift committed baselines
without touching any source path in the existing filter.
|
||
|
|
aa7ef096c9 |
fix(admin): refine focused editor experience (#2137)
* fix(admin): refine sidebar collapse control * fix(admin): synchronize sidebar brand animation * fix(admin): sync boot loader theme * fix(admin): unstick editor header * fix(admin): unify editor surface colors * fix(admin): refine editor publishing actions * fix(admin): standardize content editor field labels and spacing * fix(admin): refine content editor hierarchy * fix(admin): align featured image help tooltip * fix(admin): align configuration loading screen * fix(admin): correct editor label presentation * test(e2e): align publish action labels |
||
|
|
6c96ac8b68 |
fix(visual-editing): flush unsaved inline edits on pagehide (#1945)
* fix(visual-editing): flush unsaved inline edits on pagehide (#1582) * fix(visual-editing): address review — comment wording, robust e2e persistence check Rename the flush comment's caveat marker and harden the e2e test: wait for the PUT request (Playwright never delivers response events for a page that navigated away, so waitForResponse deadlocks on the keepalive request), then poll the rendered page until the edit is persisted before asserting on a fresh load. |
||
|
|
fbb04abcff |
fix(admin): use the configured Site Icon as the admin favicon (#1505)
* fix(admin): use site favicon (Site Icon) for the admin shell * fix(admin): prefer explicit admin.favicon over Site Icon Address review: an explicitly configured build-time admin.favicon is admin-specific and should not be overridden by the more general Site Icon. Flip precedence to admin.favicon -> Site Icon -> bundled mark. The Site Icon still brands the admin for the common case where no admin.favicon is set. * fix(admin): skip the settings read when admin.favicon is configured * fix: emit the Site Icon MIME type on the admin favicon link The media-file URL has no extension, so Chromium ignores SVG favicons without type="image/svg+xml". Adds an e2e test that sets a Site Icon via the API and asserts the admin shell links it with its MIME type. --------- Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
1c15097762 |
Refine admin editor settings panel actions (#1924)
* refactor(admin): allow routes to opt out of Shell main padding
Routes can now declare staticData.fullBleed to drop the Shell <main>'s
default p-6 padding and page scroll, managing their own scroll regions
instead. No route opts in yet; all existing routes render unchanged.
* refactor(admin): extract ContentSettingsPanel from ContentEditor
Moves the editor sidebar — all eight sections (Publish, Ownership,
Bylines, Translations, Taxonomies, SEO, Document Outline, Revisions)
plus the block-panel swap — into ContentSettingsPanel.tsx, along with
the sidebar-only BylineCreditsEditor and AuthorSelector components.
The panel is memoized: ContentEditor re-renders on every keystroke
(formData state), and the panel subtree is expensive. Handler props
are made useCallback-stable at both layers (ContentEditor internals
and the ContentEditPage/ContentNewPage inline arrows in router.tsx)
so the memo actually holds. Verified: zero panel re-renders while
typing and across a full autosave cycle.
No visual or behavioral change.
* feat(admin): structural 3-pane editor layout on desktop
Both editor routes (/content/$collection/$id and /new) opt into the
Shell's full-bleed mode. The editor form becomes a full-height flex
row: a centered max-w-3xl editor column with its own scroll, and a
320px settings panel built on Kumo Sidebar (collapsible="none",
contained) with independent scroll. The fields card chrome is gone —
fields sit directly on the page.
The panel aside stays inside the <form>, preserving native form
association for its inputs and submit buttons. Kumo Sidebar's side
prop is physical, so it's flipped explicitly for RTL — in Arabic the
nav sits right and the panel left. Below lg the panel stacks under
the fields (unchanged interim behavior; the sheet lands next).
Distraction-free mode is unchanged: the panel stays mounted but
hidden so panel-local state survives.
Verified: nested Sidebar.Provider does not interfere with the nav
sidebar's collapse; independent scroll regions; no layout jump from
/new to the edit route; RTL mirror; distraction-free enter/exit.
* feat(admin): move editor actions into a settings panel action bar
Adds a two-tier action bar pinned above the settings panel body:
autosave status and the primary action cluster (Preview, Save,
Publish/Unpublish/Publish changes) on top; publish-state badges with
Discard changes and Live View below. The status badges move here from
the panel's Publish section, giving publish state a single home.
The bar is deliberately not memoized — it absorbs the high-frequency
props (isDirty, isSaving, isAutosaving) so they never bust the
memoized panel body. The normal-mode header cluster and the
bottom-of-form duplicate Save button are removed; the editor strip
keeps only back/title/locale-badge and the distraction-free toggle.
The distraction-free hover overlay keeps its own save/publish/exit
controls since the panel is hidden in that mode.
Verified end to end: publish, pending changes via manual save,
discard from the bar's dialog, unpublish, autosave indicator,
distraction-free overlay, and native form association of panel
inputs.
* feat(admin): settings panel visual hierarchy pass
Flattens the settings panel into the structural aside: the rounded
card chrome is gone, sections are divided by full-width hairlines,
and every section heading drops to a shared muted label style (small,
uppercase, subtle) — including the headings rendered inside
TranslationsPanel (via a new optional headingClassName prop, since
it's shared with other pages), TaxonomySidebar, DocumentOutline, and
RevisionHistory, so the panel reads as one system. RevisionHistory
also loses its own nested card.
Publish keeps its prominence through position (first section, under
the action bar) and content weight. Move to Trash moves out of the
Publish section to an isolated section at the very bottom of the
panel.
Verified in light and dark themes and in Arabic (RTL mirror). No new
user-facing strings.
* feat(admin): settings sheet below lg breakpoint
The editor's Sidebar.Provider now wraps the whole editor layout
(collapsible="offcanvas", mobileBreakpoint=1024): at lg and above the
panel renders as the contained desktop pane exactly as before; below
lg Kumo renders it as an end-side slide-in sheet with a backdrop. A
Settings button in the editor strip toggles the sheet, and the strip
also keeps a Save button and autosave indicator below lg so saving
never requires opening the sheet. The contained prop flips with the
breakpoint — the desktop pane anchors inside the provider wrapper
while the mobile sheet needs viewport-fixed positioning.
Kumo's mobile sheet renders inline in the DOM (no portal), so panel
inputs and submit buttons keep native form association with zero
extra wiring — Save from inside the sheet verified end to end.
Opening a portable-text block detail panel below lg auto-opens the
sheet and restores its prior state on close (MobileBlockSidebarSync).
The panel is unmounted (not hidden) in distraction-free mode since a
mounted offcanvas pane would leave its layout gap behind.
The test browser viewport moves to 1280x800: the default 414px width
now renders the closed sheet, making panel controls unreachable for
the tests that exercise them.
Known deviation, needs manual QA in a visible browser: Kumo's sheet
dismisses on focusout, so opening a confirm dialog from the sheet may
close the sheet underneath (modal supersedes sheet); dialog state
transitions verified headless, but Escape ordering and focus return
depend on rAF-driven transitions that headless preview throttles.
* fix(admin): widen settings panel and stop byline credit overflow
The desktop settings pane grows from 20rem to 23rem; the mobile sheet
stays at 20rem so it still fits small phone viewports. Byline credit
cards no longer overflow the panel: the name block truncates, the
action buttons wrap when tight, and the panel body clamps horizontal
overflow so no section can poke past the panel edge.
* patch: spec
* test(admin): settings panel component tests + changeset
Adds component tests for ContentSettingsPanel (section visibility per
capability flags: role gating for Ownership/Bylines, hasSeo,
supportsRevisions, portableTextEditor presence, i18n, new-item vs
edit, the block detail panel swap, and trash-last ordering) and for
SettingsActionBar (publish cluster states across draft / live /
pending-changes / scheduled, new-item trimming, Live View link, and
autosave indicator states).
Adds the changeset for the content editor layout redesign.
* fix(admin): editor action bar usability nits
Three small intuitiveness fixes from review:
- The Preview button gets its text label back ("Preview" /
"Preview draft") instead of being icon-only.
- Live View moves out of the action bar's context tier into the
Publish section as a full-width row under the slug — the bar's
second tier now holds just the status badges and Discard, so a
busy post no longer crowds three controls into one strip.
- Discard changes is available again inside distraction-free mode.
The discard confirmation is extracted into a shared
DiscardDraftDialog (used by both the action bar and the
distraction-free overlay) so the copy and behavior cannot drift —
caught by the pre-commit review.
* fix(admin): clarify editor action ownership
* fix(admin): restore editor publish panel layout
* fix(admin): consistent editor panel headings and metadata footer
Aligns every settings panel section heading (Ownership, Bylines, Taxonomies, SEO, Outline, Revisions) with the Publish heading style, and moves the Created/Updated timestamps into a separated metadata footer so they read as read-only info instead of publish controls.
* fix(admin): stop SEO canonical URL field overflowing the settings panel
Wraps the Canonical URL input like the Meta Description field so its long description text wraps within the sidebar instead of forcing horizontal overflow.
* fix(admin): stop settings panel inheriting sidebar's nowrap
The Kumo Sidebar wrapper sets whitespace-nowrap for its collapse animation, which inherited into the settings panel and stopped long SEO field descriptions (e.g. Canonical URL) from wrapping, overflowing the panel. Resets white-space on the panel root. Reverts the ineffective SeoPanel wrapper div.
* fix(admin): drop empty taxonomy section in settings panel
TaxonomySidebar returns null when no taxonomies apply to the collection, but the panel always wrapped it in a bordered p-4 block, leaving an empty section. Moves the section chrome into TaxonomySidebar so it renders nothing when empty.
* fix(admin): put status label and badges on one line
Renders the Status label inline with its badges instead of stacking them, and uses a badge for the no-drafts status for visual consistency.
* fix(admin): add breathing room between status label and badges
* refactor(admin): merge autosave status into the Save control
The settings action bar, the below-lg editor header, and the distraction-free overlay each rendered a passive autosave "Saved" indicator next to a Save button that also read "Saved" when clean -- duplicating the status. Consolidate into the single SaveButton three-state control (Saved / Save / Saving...), fold autosave into its saving state, and remove AutosaveIndicator.
* fix(admin): make saved button feedback transient
Keep the merged Save control, but make Saved a brief completion confirmation instead of the clean resting state. The button now returns to disabled Save after the confirmation pulse, and new edits or saving progress interrupt the pulse immediately.
* fix(admin): smooth Save button state transitions
Keep the Kumo button as the control, but render icon and label as a stable overlaid content stack. Crossfade the Save, Saving, and Saved states with a short ease-out blur/scale transition, preserve reduced-motion behavior, and transition only button color properties instead of using transition-all.
* fix(admin): stabilize Save button loading feedback
Delay the visual Saving state to avoid spinner flashes on fast saves, keep it visible briefly once shown, and preserve the blur/scale transition between every visible SaveButton state.
* fix(admin): keep byline actions inline
Keep byline credit actions in the card header next to the byline identity by using a fixed two-column header layout and preventing the action group from wrapping below the name.
* chore: keep editor sidebar plan local
* feat(admin): redesign editor settings actions
* fix(admin): color saved feedback as success
* feat(admin): update RevisionHistory and DocumentOutline components
* fix(admin): align editor sidebar controls
* feat(admin): update components to use Text for headings and improve layout
* feat(admin): animate editor sidebar accordions
* fix(admin): stabilize editor settings interactions
* fix(admin): scope save feedback to editor writes, keep panel mounted in distraction-free
* fix(admin): keep block-panel sheet suspended in distraction-free, isolate auxiliary writes
* fix(admin): consistent corner radius hierarchy in editor settings panel
* fix(admin): align editor panel content to a consistent 16px gutter
* fix(admin): drop double gap above the trash section
* chore(admin): trim excessive comments in editor panel
* fix(admin): serialize editor content operations
* Revert "fix(admin): serialize editor content operations"
This reverts commit 52977b9a95fd7bbf82fdf80fd1f47d4acce98a43.
* fix(admin): preserve editor operation semantics
* fix(admin): preserve editor controls across layouts
* refactor(admin): simplify editor save feedback
* fix(admin): animate save button state changes
* chore(admin): format save button changes
* fix(e2e): update selectors for editor settings panel UI
Align E2E assertions with the merged SaveButton live region, Save/Saved labels, and updated byline/revision class names.
* chore(e2e): format content-types selector update
|
||
|
|
99b8a33b2e |
fix(media): refine media library and asset details UX (#1824)
* fix(media): refine media library and asset details UX * fix(media): cancel stale detail dialog close timer * fix(media): scope active query state to local filters * fix(media): refine view toggle tests and provider deletes |
||
|
|
1866fa346e |
fix(core): send Cache-Control private, no-store on the admin shell (#1741)
* fix(core): send Cache-Control private, no-store on the admin shell * test(e2e): assert admin shell sends Cache-Control private, no-store |
||
|
|
b4d7228d5e |
fix(core): scope admin stylesheet to its route in dev (#1593)
The admin shell imported the compiled Kumo/Tailwind theme as a top-level CSS module. In astro dev, Astro injects every CSS module in the project graph into every page's <head>, so the admin theme leaked onto public routes and overrode host :root tokens. Import it as ?url and emit a route-scoped <link> instead; make the Vite styles.css alias a regex so the ?url form still resolves to dist. Closes #1281 |
||
|
|
a623c6b7db |
fix(media): optimize storage-backed images behind Cloudflare Access (#1549)
Wrap Astro's image endpoint so EmDash media bytes are read straight from the storage adapter instead of being fetched over HTTP. The stock endpoint fetches the (absolute) media URL to load the source; on Cloudflare that is a self-subrequest that fails behind Access / global_fetch_strictly_public, 404ing from /_image. The wrapped endpoint matches the internal media route by pathname, reads bytes from storage (no fetch), and transforms them with sharp (Node) or the IMAGES binding (Cloudflare); everything else delegates to the stock endpoint unchanged. On by default where the image service is local; opt out with images: false. |
||
|
|
fb31240d64 |
fix(admin): add avatar picker to byline editor (#1250) (#1486)
The byline avatarMediaId field was supported by the model and API but had no admin control, so avatars could only be set programmatically and every UI edit cleared a previously-set value. Adds a media picker to the byline editor and includes avatarMediaId in the create and update payloads. |
||
|
|
d6269e7eb6 |
feat(i18n): add en-GB (British English) locale (#1411)
* feat(i18n): add en-GB (British English) locale Adds an English (UK) locale catalogue: British spelling and morphology only (analyse, authorise, licence-the-noun, Align Centre, Search Engine Optimisation, etc.). ICU placeholders untouched. 19 strings differ from en; all other msgstr values mirror the source so coverage is 100%. Changeset included. Follows the documented locales.ts workflow; lingui compile passes. * test(e2e): match exact language names in language switcher test The en-GB locale adds a second option containing "English", so the substring match in the language switcher test now violates Playwright's strict mode. Match the exact accessible name instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: MEM Digital <memdigital@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
850c1b7e23 |
feat: responsive srcset for media via astro:assets (#1438)
* feat: responsive srcset for media via astro:assets Route locally/R2-stored media through Astro's configured image service (astro:assets) so the Image and Portable Text image components emit a responsive srcset and modern formats instead of a single full-size img. Auto-registers image.remotePatterns for the storage publicUrl host, the site origin (scoped to the media route) when siteUrl is set, and a dev-only host-agnostic media pattern so it works in astro dev with no config. Falls back to a plain img when optimization isn't possible (no service, unauthorized host, unknown dimensions), so existing sites are unaffected. * fix: address review feedback on responsive images - Update the visual-editing e2e assertion: optimized media srcs are now image-service URLs (/_image, /cdn-cgi/image) that encode the media path, so decode before matching MEDIA_FILE_PATTERN. - buildImageRemotePatterns: only authorize http(s) public URLs, and scope the pattern to the path prefix when the public URL has one (avoids authorizing the whole CDN host). - Source the components' breakpoint list from the shared RESPONSIVE_BREAKPOINTS constant instead of duplicating it. - Add unit coverage for buildImageRemotePatterns. * fix: ban cross-origin escapes in toAbsoluteMediaUrl (SSRF guard) toAbsoluteMediaUrl resolved any root-relative path against the origin, but protocol-relative URLs (//evil.com/x) and backslash tricks (/\evil.com) also start with / and resolve to a different origin -- a classic SSRF vector once an image-service remotePattern authorizes the media path. Only return the absolutized URL when it stays on the same origin; otherwise leave it untouched (and buildResponsiveImage then skips it, since it only accepts absolute http(s) URLs). Adds a toAbsoluteMediaUrl describe block covering same-origin resolution, already-absolute/empty/non-path passthrough, and the protocol-relative + backslash SSRF guards. |
||
|
|
cedfcc527d |
[bot] Fix #1242: Admin content edit route should preserve locale for slug-based i18n entries (#1307)
* fix(admin/core): forward locale query param through content edit route and write handlers (#1242) * fix(admin): carry locale through new-content save navigation The create-new flow navigated to the edit route without a locale search param, while edit links and translation navigation now append one. That asymmetry made the i18n e2e test's full-URL equality assertion fail when navigating back to the default-locale entry. * fix(core): resolve locale-update overlap with #1302 after rebase Main's #1302 already added locale reading to the PUT content-update route. Drop this branch's duplicate declaration (kept main's request.url form, which the existing route test exercises) so the merged PUT handler has a single `locale`. The branch's remaining core contribution is locale on DELETE and the publish/unpublish/schedule/ discard-draft sub-action routes. * Fix e2e URL assertions for locale-preserving edit route The edit route now carries a ?locale= search param (#1242), so the CONTENT_EDIT_URL_PATTERN assertions in the i18n, content-crud, field-widgets, and bylines specs (which anchored on the ULID) need to tolerate an optional query string. Also forward locale on the content list title link, matching the pencil edit button, so clicking the title scopes the editor to the entry's locale instead of falling back to the default. --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com> Co-authored-by: Matt Kane <m@mk.gg> Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
f4dd4b049c |
test(e2e): correct the CF-skip characterizations after investigating (#1327)
Matt's point — miniflare D1 is a local SQLite file, strongly consistent, so a read-after-write 'lag' isn't possible; a missed read would be a real code bug. Investigated both: - i18n translations: reproduced against live D1 — the backend is CORRECT (the translations API returns both siblings, translation_group is right). The failure is front-end render timing on the slower workerd dev runtime, not a D1 bug. Comment corrected. - invite-flow: couldn't reach the user-creation path without the passkey ceremony; re-characterized as a possible real SQLite-vs-D1-dialect bug needing a browser-driven repro, no longer hand-waved as a dev consistency artifact. |
||
|
|
3adc6031a9 |
test: harden silently-passing tests, prune framework-only and duplicate ones (#1339)
* test: harden silently-passing tests, prune framework-only and duplicate ones Audit-driven cleanup of low-value and misleading tests: - Fix silent-skip tests that passed when the asserted behavior was broken: device-flow Token Revoke (missing expect before the success guard), theme-unit-test gutenberg conversions (assertions gated on if(post)), and two e2e specs (publish status, plugins/wp-import "no error") that passed on a blank page. - Rewrite plugins/hooks.test.ts sorting/beforeSave tests to actually execute the pipeline and assert ordering, dependency order, and content chaining instead of only checking getHookCount. - Strengthen field structural validators (image/file/portableText) and the preview DO no-op transaction test with real reject/assert cases. - Delete input-rules.test.ts (tested vanilla TipTap, not EmDash) and remove tautological/duplicate tests (auth-atproto descriptor invariants, do-config passthrough dupes, SignupPage/MediaDetailPanel/invite-flow/ settings-pages duplicates). * test: address review — close sqlite handle, tighten classic-editor selector, drop stray demos types - hooks.test.ts: close the better-sqlite3 handle in afterEach (matches repo pattern) - theme-unit-test: select a classic-category post with no wp: block markers so the classic-HTML fallback path is genuinely exercised - revert an unrelated regenerated demos/cloudflare/emdash-env.d.ts that was swept in accidentally; merged latest main so the already-merged #1336 vite-config/changeset no longer show in this PR's diff |
||
|
|
9ab1e7807e |
test(e2e): run the full suite against the Cloudflare/workerd target (#1322)
* test(e2e): wire marketplace + noop sandbox into the CF fixture Mirrors the Node fixture: reads EMDASH_MARKETPLACE_URL and uses the runtime- agnostic noop sandbox runner (createNoopSandboxRunner, no cloudflare:workers import) so the marketplace browse/detail specs run against the workerd target. Avoids the CF sandbox()/LOADER-binding setup, which is only needed at plugin install time, not for the browse UI. * test(e2e): run the full suite against Cloudflare, sharded Expands the CF lane from the content-types smoke to the whole e2e suite (4 shards; fewer than the Node lane's 8 because each shard boots its own workerd dev server). 235/237 specs pass on the workerd runtime. Skips one CF-specific failure: 'invited user appears in the users list' — after a passkey-invite registration the user isn't read back on workerd (passes on Node, reproducible in isolation). Suspected D1 Sessions read-after-write under miniflare; flagged for maintainers, skipped so the lane stays green. * test(e2e): skip i18n translation-sibling check on Cloudflare CI surfaced a second CF-specific failure with the same write-then-read signature as the invite-flow skip: on the freshly created FR translation page the EN sibling isn't read back in time, so its 'Edit' link doesn't render — though the translation is created and navigable (the other i18n specs pass). Suspected D1 Sessions read-after-write under miniflare dev; flagged for maintainers, skipped on CF so the lane stays green. * ci: shard the Cloudflare e2e lane 8 ways like the Node lane Wall-clock breakdown showed per-shard setup is only ~30s (dev-server boot + seed + warm); the ~8min was test execution, not boot overhead. So 4 shards left it at ~9min vs the Node lane's ~5min. Tests parallelize near-linearly — 8 shards brings it in line. |
||
|
|
28432b9b5a |
feat: extensible bylines (#1258)
* feat(bylines): types and storage interfaces for custom fields * feat(bylines): migration 041 for custom field tables * test(migrations): assert byline-field tables in dialect-compat fresh-run * feat(bylines): BylineSchemaRegistry with version counter * feat(bylines): per-isolate field-defs cache + request-cache invalidation helper * feat(bylines): atomic version bumps around schema mutations for cache coherence * feat(bylines): hydrate customFields in BylineRepository * feat(bylines): batched customFields hydration for getBylinesForEntries * feat(bylines): registry helpers, error codes, and reorder slug reservation for admin API * feat(bylines): zod schemas for byline custom-field admin API * feat(bylines): handler layer for byline-fields and update routes * feat(bylines): admin API routes for byline custom fields * test(bylines): admin API coverage for byline custom fields * fix(admin): RTL-safe spacing, Lingui placeholder, error states for byline-schema * test(admin): byline-schema permission + sidebar visibility coverage * feat(admin): API client for byline custom-field schema * feat(admin): register /byline-schema route * fix(bylines): editors can read byline field defs via schema:read * feat(admin): custom field inputs in byline edit form * test(admin): byline edit form forwards customFields on save * test(e2e): byline custom fields round-trip + changeset + query-counts snapshot * feat(bylines): accept customFields on POST create route * feat(admin): inline byline custom fields and surface schema link in page header * refactor(admin): drop Byline Schema entry from sidebar * fix(bylines): translatable hydration, url scheme, atomic create+update, D1 recovery * fix(bylines): parity-aware dirty + always-advance clean for the field-defs cache * fix(admin): unify byline-fields cache key and harden custom-field inputs * ci: update query-count snapshots * fix(bylines): qualify options.value in version SQL for postgres * fix(bylines): lint * fix(bylines): e2e test fix * Update packages/core/src/database/repositories/byline.ts Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> * fix(bylines): restore success return in coerceFieldValue url case * feat(bylines): cache field-defs promise to coalesce concurrent reads * style: format --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com> Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
886f2d1e49 |
fix(admin): add search to the byline picker and remove the 100-byline cap (#1225)
* fix(admin): add search to the byline picker and remove the 100-byline cap The content-entity byline picker was a plain Select over the first 100 bylines with no search, so bylines past the first page were unreachable and a credited byline outside that page failed to render at all. Replace it with a debounced server-side search (fetchBylines already supports search + cursor) and resolve credited bylines from the saved entry so they always render. Closes #1217. * fix(admin): repair byline picker test harness, e2e flow, and render-phase ref - Add QueryClientProvider to the shared admin test render wrapper so components using useQuery (BylineCreditsEditor) mount without throwing 'No QueryClient set'. - Rewrite the bylines e2e spec to drive the search-input picker (debounced server search + clickable result list) instead of the removed Kumo Select, waiting for each credit to commit and reordering via the row-scoped Up button. - Replace render-phase ref mutation in BylineCreditsEditor with a useMemo-built lookup map (concurrent-safe). - Surface a localized inline error when the byline search query errors instead of falling through to the empty state. - Add missing translationGroup to the makeByline test fixture. --------- Co-authored-by: Matt Kane <m@mk.gg> |
||
|
|
b9cc08e755 |
chore: bump @cloudflare/kumo to 2.3 (#1177)
* chore: bump @cloudflare/kumo to 2.3 Migrates two internal call sites to the Kumo 2 API: - accordion block: Collapsible refactored to a compound component. Use Collapsible.Root / .DefaultTrigger / .DefaultPanel instead of <Collapsible label=...>. - chart block: ChartPalette.color renamed to ChartPalette.categorical. Admin tests that asserted on Button's native title attribute now read aria-label instead, because Kumo 2 wraps <Button title> in a Tooltip popup rather than setting the DOM title attribute. Updated the @cloudflare/kumo mock in the blocks renderer tests to match the new Collapsible compound shape. No public API changes; consumers see identical behaviour. * test(e2e): update title selectors for Kumo 2 Button tooltip Kumo 2.x wraps <Button title> in a Tooltip popup rather than setting a DOM title attribute. Switch redirect and revision history e2e selectors from button[title=...] to button[aria-label=...] / [aria-label^=...]. The components already exposed accurate aria-labels (and in the redirects case, more specific per-row labels via the source path). |
||
|
|
d4377338fc |
chore: clean up oxlint warnings across packages (#1140)
* chore: clean up oxlint warnings across packages Addresses 47 of 56 type-aware lint warnings, leaving only the plugin-cli pipeline.ts unsafe assertions which warrant a separate Zod-driven PR. Changes by category: - preserve-caught-error: add `cause` to thrown error in registry handler - no-unused-vars: drop unused `Link` import, prefix unused e2e vars - no-shadow: rename locals shadowing Lingui macros (`msg`, `plural`) - no-unnecessary-type-assertion: remove redundant casts and `!` where prior `typeof` checks or tsconfig settings already narrow the type - atproto-test-utils: add to test-override file glob (test factories legitimately produce branded types) - contentful-to-portable-text: add runtime type guards in types.ts (`isContentfulLinkPayload`, `isContentfulSysEnvelope`, `parseAssetFile`, `getStringField`, `getRecordField`) and use them throughout, replacing scattered `as` casts with validated narrowing - adapt-sandbox-entry: extract `normalizeRouteEntry` helper, simplify headers normalization (`ctx.request` is always a real Request in the in-process adapter), narrow remaining boundary casts to single disable comments with rationale - admin/api/registry: use the shared `parseApiResponse` helper for install endpoint; localStorage cache parse uses a type guard * test: drop unused byline IDs in bylines.spec.ts The `_firstBylineId` / `_secondBylineId` names read as if the values themselves are unused, but the test actually relies on the `createByline` side effects (the bylines need to exist before the combobox can find them by name). Drop the bindings and add a comment explaining the intent. |
||
|
|
49b66d910c |
fix(admin): remove sticky editor header (#955)
* fix(admin): remove sticky editor header The sticky save header had multiple compounding problems: - Backdrop-blur transparency let scrolled content show through, looking visually broken when columns scrolled at different rates - Fragile negative-margin trick (-mx-6 -mt-6) had to perfectly cancel parent padding; broke when ancestors changed - z-index conflicts with the app bar header above - ~85px of permanent vertical chrome on every editor page The save action is already reachable via the form's bottom Save button and the standard Cmd+S keyboard shortcut, so the sticky variant wasn't earning its cost. Drop it across ContentEditor, ContentTypeEditor, SectionEditor, and the settings sub-pages. Preserve the distraction-free mode hover-revealed overlay in ContentEditor (different use case: keep chrome out of the way while writing). Drops the EditorHeader `sticky` prop entirely. Updates the test to remove the now-irrelevant sticky-class assertions. * fix(admin): add bottom-of-form Save buttons; correct doc claims Address PR review feedback (#955): the previous commit claimed save was 'always reachable via in-form Save button + Cmd+S' but neither was true for ContentEditor or SectionEditor (header was the only Save button, no Cmd+S handler exists). - ContentEditor: add a bottom-of-form SaveButton (mirrors the header state, last interactive control in DOM order) - SectionEditor: add a bottom-of-form SaveButton (after the content editor, before the sidebar) - Update doc comments and changeset to describe what's actually true (bottom save button per editor; no Cmd+S claim) - Update ContentEditor.test.tsx queries to use .first() since there are now two Save buttons matching the same role/name (both submit the same form, so either works for the assertion) * fix(admin): move bottom Save button into main editor column The previous commit put the Save button at the absolute bottom of the form -- after both the main editing column AND the entire sidebar (Publish/Status/Taxonomies/SEO/Outline/Revisions). Visual testing showed users had to scroll past the whole sidebar to reach it; way too hidden to be discoverable. Move the bottom SaveButton inside the main column (lg:col-span-2 in ContentEditor, col-span-8 in SectionEditor) so it appears right after the body field, where users naturally land when they finish editing. SectionEditor was already correct -- just the ContentEditor placement was wrong. * test(e2e): scope Save button queries to .first() now that editors have two Editor pages now render two SaveButtons (one in the header, one at the bottom of the main column). Both submit the same form, so playwright's strict-mode click can target either, but the unscoped queries ('button:has-text("Save")', 'getByRole("button", { name: "Save" })') match both elements and fail. - e2e/fixtures/admin.ts: clickSave() and waitForSaveComplete() now use .first() on the Save / Saved button locators. - e2e/tests/form-data-loss.spec.ts: 3 inline Save queries updated to .first(). Other E2E save references are either dialog-scoped (redirects), text 'Save Changes' instead of 'Save' (content-types), or on bylines panel which has only one save button -- those are unaffected. |
||
|
|
a4968c1057 |
fix(admin): replace raw <select> and <input type="search"> with Kumo components (#950)
* fix(admin): replace raw <select> and <input type="search"> with Kumo Select / Input
Migrates 11 raw <select> elements (marketplace filters/sort, redirects,
sections filter, content editor byline picker, content picker modal,
field editor type, repeater sub-field, plugin block fields, WordPress
import user mapping, bylines routes) to Kumo Select. Where a paired
<label> existed, drop it and use Select's label prop. Where options come
from plugin/runtime data, use <Select.Option> children to preserve
flexibility; where they're a fixed translatable set, use the items
prop with t-wrapped labels.
Also migrates 2 marketplace search inputs (<input type="search">) to
Kumo Input with type="search". Drops redundant border/bg/padding
classes that Kumo provides automatically.
LocaleSwitcher.tsx is intentionally custom (size variants don't fit the
Select API) and stays as a raw <select>.
* fix(admin): use Select items prop for label resolution
Visual testing revealed that <Select> with only <Select.Option> children
displays the raw value in the trigger (not the label). Kumo's <Select>
needs the items prop (or a renderValue callback) to resolve trigger
labels.
Affected:
- routes/bylines.tsx: filter type select showed raw 'all', linked-user
select was empty/zero-width
- WordPressImport.tsx: per-user mapping select would show raw user IDs
Both now use items={...} for the label map. Also wraps the bylines
filter Select in a flex-1 container so it grows to fill the row.
* fix(admin): address PR review feedback on Select migrations
- Convert CAPABILITY_LABELS in lib/api/marketplace.ts from plain strings
to MessageDescriptor map; describeCapability resolves via i18n._(...).
Fixes the i18n bug where t`${label}` couldn't be extracted by Lingui.
Updates PluginManager consumers to call t(label) on the descriptor.
- Convert ThemeMarketplaceBrowse SORT_LABELS to MessageDescriptor too,
matching MarketplaceBrowse pattern.
- Restore w-48 width class on WordPressImport user-mapping Select to
prevent layout shift in the per-user mapping list.
- Restore empty placeholder option ('Select...') on RepeaterField,
PortableTextEditor DynamicSelect, and BlockKitFieldWidget select
renderers so users can clear the field.
- Update e2e/tests/bylines.spec.ts to use combobox + option click
pattern instead of Playwright's native selectOption (Kumo Select is
a custom button + popover, not a native select).
|
||
|
|
71f4e7d85b |
feat: i18n menus and taxonomies (#916)
* feat(i18n): add support for menus and taxonomies * fix(migration 036): block rollback on multi-locale installs * test(migration 036): cover data remap and rollback * fix(menus): surface name in NOT_FOUND, detect CONFLICT without locale * fix(seed): set translation_group on menu, item, and taxonomy_def inserts * fixup! fix(seed): set translation_group on menu, item, and taxonomy_def inserts * style: format * ci: update query-count snapshots * feat(seed): i18n round-trip for menus and taxonomies * fix: update migration 036 to resolve locale via i18n config and add regression tests for non-default locales * style: format * chore(menus,taxonomies): tidy module conventions * feat: implement i18n menu cloning and migration refinements for locale-aware references * fix: update migration references to 036 in menus and add documentation for pending tasks * feat: extend featured_image schema with metadata and update e2e test fixtures * ci: update query-count snapshots * test: mock useSearch hook in MenuEditor tests * fix(menus): use defaultLocale in CONFLICT guard and document seed item caveat --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com> Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
491aeec5a6 |
Sticky Save buttons across editors (#854)
* fix(admin): consistently place sticky Save buttons across editor pages The Content editor, Section editor, Content Type editor, and Settings sub-pages now render their primary save action in a sticky top-right header so users always see whether changes are unsaved while scrolling long forms. The existing bottom-of-form save buttons are preserved so keyboard and screen-reader users still encounter a save action as the last interactive control on the page (DOM order is unchanged), per @afercia's feedback on the issue thread. Introduces a shared `EditorHeader` component for editor pages that want the same sticky-header pattern. Fixes #233 * fix(admin): scope settings e2e Save locators with .first() and correct EditorHeader RTL doc Two checks were failing on PR #854: 1. `E2E tests (7/8)` — four tests in `e2e/tests/settings-pages.spec.ts` failed with Playwright strict-mode locator violations: strict mode violation: locator('button').filter({ hasText: 'Save Social Links' }) resolved to 2 elements strict mode violation: locator('button').filter({ hasText: 'Save SEO Settings' }) resolved to 2 elements The new sticky-header save buttons in `SocialSettings.tsx` and `SeoSettings.tsx` deliberately reuse the bottom-of-form button text so keyboard / screen-reader DOM order ends on a save action. Both buttons submit the same form via the `form` attribute association, so it's safe to scope the locator with `.first()`. 2. Copilot reviewer pointed out that `EditorHeader.tsx`'s RTL doc comment claimed the component uses logical `start-*`/`end-*`/`ms-*`/`me-*` utilities, but the implementation uses only symmetric `-mx-*` / `px-*` (which are direction-agnostic). Updated the comment to describe what the component actually guarantees, and call out that callers passing directional content into the slots should use logical classes. --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
31333dc593 |
feat: pluggable auth providers — add AT Protocol, refactor GitHub/Google (#398)
* feat: pluggable auth providers — add AT Protocol, refactor GitHub/Google
Introduces a pluggable auth provider system and uses it to add AT Protocol
authentication as the first plugin-based provider. GitHub and Google OAuth
are refactored from hardcoded buttons into the same provider interface.
- AuthProviderDescriptor interface with admin UI, routes, and public routes
- virtual:emdash/auth-providers Vite module for distributing provider components
- Shared findOrCreateOAuthUser() in @emdash-cms/auth for consistent signup gating
- AT Protocol auth via @atcute/oauth-node-client with PKCE (public client)
- allowedDIDs and allowedHandles config with independent handle verification
via DNS-over-HTTPS + HTTP well-known (never trusts PDS handle claims)
- Default role for new signups changed to Subscriber
- First user becomes Admin during setup regardless of provider
* fix: address PR review — TOCTOU race, ensureTable memoization
- Replace countUsers() with setup_complete option flag check in both
GitHub/Google and ATProto OAuth callbacks to prevent concurrent
callbacks from both claiming first-user admin role
- Memoize ensureTable() in db-store.ts with a module-level boolean
so CREATE TABLE IF NOT EXISTS only runs once per process
* style: format
* fix: address PR review — split auth-atproto package, add provider storage, update terminology
Addresses all 10 review comments on PR #398:
- Split auth provider into @emdash-cms/auth-atproto (npm-installable),
keep syndication plugin in @emdash-cms/plugin-atproto (marketplace)
- Add `storage` field to AuthProviderDescriptor, reuse plugin storage
infrastructure instead of manual SQL table creation
- Rename "AT Protocol" → "Atmosphere", remove "PDS" from user-facing strings
- Forbid self-signup when no allowlists configured (except first admin)
- Fix core callback.ts import to use #db alias
- Fix env.d.ts to reference emdash/locals package
* fix: CI failures — typecheck, lint, bundle, and i18n catalog
- Fix auth-atproto typecheck by aligning tsconfig with core's settings
(noUncheckedIndexedAccess, lib targets) and adding explicit return type
to storage.ts to avoid cross-package type portability issue
- Add @types/react to core devDependencies for auth/types.ts React refs
- Fix plugin bundle CLI: clean stale temp dirs before bundling, and
expand TS_EXT_RE to strip .mjs/.js/.cjs extensions from dist entries
- Fix floating promise lint error in InlinePortableTextEditor
- Regenerate Lingui catalog to include interpolated "Sign in with {0}"
* fix: update tests and plugin config for pluggable auth changes
- Fix atproto plugin: add build step, tsdown dep, and point exports to
dist files so plugin validation passes
- Update LoginPage browser test to mock fetchAuthMode instead of the
removed fetchManifest
- Update SetupWizard browser test: step label changed from "Passkey"
to "Sign In"
- Update E2E tests: passkey step text changed from "Set up your passkey"
to "Choose how to sign in"
* fix(i18n): wrap missing "Back to login" string with Lingui t tag
* fix: address PR review — remove singleton, use getPublicOrigin
Remove module-scope singleton from OAuth client (unsafe on Workers
where module vars persist between requests). Construct per-request.
Replace url.origin with getPublicOrigin() in all atproto routes so
the correct origin is used behind reverse proxies. Export
getPublicOrigin from route-utils. Narrow the EmDashConfig import in
public-url.ts to an inline interface to avoid pulling the full core
type tree into auth-atproto's typecheck.
* fix(test): add missing virtual:emdash/config mock in mcp-discovery-post test
The test was missing a mock for the virtual:emdash/config module
imported by auth middleware, causing a module resolution error.
* fix(auth-atproto): suppress no-unsafe-type-assertion lint warnings
Add eslint-disable comments for type assertions required by the
@atcute SDK's type signatures and Astro's opaque locals types.
* style: format
* fix: address PR review — use Kumo components, rename label to Atmosphere
- Replace raw HTML form elements with Kumo Input, Button, and LinkButton
in atproto admin components
- Update GitHub and Google LoginButtons to use Kumo LinkButton
- Rename auth provider label from "AT Protocol" to "Atmosphere"
- Improve oauth-client.ts loopback comment to cite RFC 8252 §8.3
- Remove broken client-side localhost→127.0.0.1 redirect that reset
the setup wizard; server-side normalization handles this correctly
* fix(auth-atproto): use Button not LinkButton for LoginButton
Per ascorbic review: LinkButton is for actual links; LoginButton
expands the inline sign-in form on click, so it's a Button.
* fix(auth): restore findOrCreateOAuthUser export after upstream merge
Auto-merge with upstream/main reverted our pluggable-auth refactor —
findOrCreateOAuthUser and the CanSelfSignup type were silently removed
from packages/auth/src/index.ts, and consumer.ts was rolled back to the
private findOrCreateUser shape. auth-atproto's callback route depends on
these exports, so typecheck was failing.
Restored both files from branch tip 4faa43b. The User-Agent fix from
upstream commit
|
||
|
|
8221c2a3a3 |
fix(admin): unblock the saving of Content Types settings (#601)
* unblock the saving of Content Types settings and display errors if saving fails * adds a test case for saving Content Types setting * restore original state after test * make the new Toast string translatable |
||
|
|
913cb62395 |
feat(admin): add full RTL support to admin UI (#565)
* chore: update .gitignore, remove stale backup file Analysis of messages.po~ The backup file is outdated: - messages.po: 5,519 lines (159KB) - current, fully extracted - messages.po~: 749 lines (19KB) - old backup, incomplete Key differences: - The ~ file has only 749 lines vs 5,519 in the current file - The ~ file is dated Apr 14 04:14 (same timestamp, so created during the same operation) * feat(locales): add dir to LocaleDefinition type * feat(locales): set html dir attribute with getLocaleDir + simple tests to prevent drift from expected output * feat(locales): add LocaleDirectionProvider for RTL support Introduces LocaleDirectionProvider component that manages text direction and language attributes for RTL locales like Arabic. Implementation: - LocaleDirectionProvider wraps Kumo's DirectionProvider - Syncs document.documentElement.dir and lang attributes with current locale - Integrates with useLocale hook for locale state - Properly ordered: I18nProvider → LocaleDirectionProvider Features: - Automatic direction switching (LTR ↔ RTL) on locale change - Single source of truth for locale → direction mapping - Updates both HTML lang and dir attributes (WCAG compliance) - Comprehensive test coverage with cleanup hooks Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor(admin): use direction-aware classes in content editor Replace non-direction-aware Tailwind classes with direction-aware equivalents in editor components to support RTL layouts: - pl-* → ps-* (padding inline start) - ml-* → ms-* (margin inline start) - mr-* → me-* (margin inline end) - left-* → start-* (positioning - CSS only, not FloatingUI placement) - right-* → end-* (positioning - CSS only) - text-left → text-start (text alignment) - border-l → border-s (border inline start) Note: FloatingUI placement values (e.g., "left-start") remain unchanged as they are library-specific enums, not directional CSS classes. Files updated: - ContentEditor.tsx (5 changes) - PortableTextEditor.tsx (3 changes) - RepeaterField.tsx (1 change) - editor/DocumentOutline.tsx (5 changes) - editor/BlockMenu.tsx (6 changes) - editor/ImageNode.tsx (2 changes) - editor/ImageDetailPanel.tsx (1 change) - editor/PluginBlockNode.tsx (1 change) - editor/DragHandleWrapper.tsx (no CSS changes - FloatingUI placement kept as-is) * refactor(admin): use direction-aware classes in media management Replace non-direction-aware Tailwind classes with direction-aware equivalents in media management components to support RTL layouts: - left-* → start-* (positioning) - right-* → end-* (positioning) - pl-* → ps-* (padding inline start) - pr-* → pe-* (padding inline end) - ml-* → ms-* (margin inline start) - text-left → text-start (text alignment) - text-right → text-end (text alignment) - border-l → border-s (border inline start) Files updated: - MediaLibrary.tsx (11 changes) - MediaPickerModal.tsx (8 changes) - MediaDetailPanel.tsx (2 changes) * refactor(admin): use direction-aware classes in content list Replace non-direction-aware Tailwind classes with direction-aware equivalents in content list and picker components to support RTL layouts: - left-* → start-* (positioning) - right-* → end-* (positioning) - pl-* → ps-* (padding inline start) - mr-* → me-* (margin inline end) - text-left → text-start (text alignment) - text-right → text-end (text alignment) Files updated: - ContentList.tsx (13 changes) - ContentPickerModal.tsx (4 changes) * refactor(admin): use direction-aware classes in users & comments Replace non-direction-aware Tailwind classes with direction-aware equivalents in user and comment management components to support RTL layouts: - left-* → start-* (positioning) - right-* → end-* (positioning) - pl-* → ps-* (padding inline start) - ml-* → ms-* (margin inline start) - text-left → text-start (text alignment) - text-right → text-end (text alignment) - border-l → border-s (border inline start) Files updated: - users/UserList.tsx (7 changes) - users/UserDetail.tsx (2 changes) - users/InviteUserModal.tsx (1 change) - comments/CommentInbox.tsx (11 changes) - comments/CommentDetail.tsx (1 change) * refactor(admin): use direction-aware classes in plugins & marketplace Replace non-direction-aware Tailwind classes with direction-aware equivalents in plugin and marketplace components to support RTL layouts: - left-* → start-* (positioning) - right-* → end-* (positioning) - pl-* → ps-* (padding inline start) Files updated: - MarketplaceBrowse.tsx (2 changes) - ThemeMarketplaceBrowse.tsx (2 changes) - MarketplacePluginDetail.tsx (4 changes) - ThemeMarketplaceDetail.tsx (4 changes) - SandboxedPluginPage.tsx (1 change) * refactor(admin): use direction-aware classes (phases 6-8) Complete refactoring to direction-aware Tailwind classes across remaining admin components to support RTL layouts. Replaced non-direction-aware classes with direction-aware equivalents: - text-left/right → text-start/end - pl-*/pr-* → ps-*/pe-* - ml-*/mr-* → ms-*/me-* - left-*/right-* → start-*/end-* - border-l/r → border-s/e - border-l-*/r-* → border-s-*/e-* Phase 6 - Schema & Content Types (3 files): - ContentTypeList.tsx - ContentTypeEditor.tsx - FieldEditor.tsx Phase 7 - Taxonomy, Menus, Sections & Widgets (8 files): - TaxonomyManager.tsx - TaxonomySidebar.tsx - MenuEditor.tsx - MenuList.tsx - Sections.tsx - SectionPickerModal.tsx - Widgets.tsx - Redirects.tsx Phase 8 - Import, Core UI & Miscellaneous (8 files): - WordPressImport.tsx - Header.tsx - Sidebar.tsx - RevisionHistory.tsx - SeoImageField.tsx - WelcomeModal.tsx - settings/AllowedDomainsSettings.tsx - routes/bylines.tsx All 177 occurrences across 48 files refactored. * refactor(admin): fix remaining icon spacing classes for RTL Convert remaining ml-/mr- icon spacing classes to direction-aware ms-/me-: - MarketplaceBrowse.tsx - LoginPage.tsx - ThemeMarketplaceDetail.tsx - SetupWizard.tsx - ThemeMarketplaceBrowse.tsx - CapabilityConsentDialog.tsx - PluginManager.tsx - users/RoleBadge.tsx - MarketplacePluginDetail.tsx These were missed in the initial batch refactor as they were primarily used for icon spacing within buttons and labels. * build: upgrade Kumo to v1.18.0 via catalog Update @cloudflare/kumo from 1.16.0/1.10.0 to 1.18.0 across admin and blocks packages using pnpm catalog for consistent versioning. Changes: - Add @cloudflare/kumo: ^1.18.0 to pnpm-workspace catalog - Update packages/admin to use catalog: (from ^1.16.0) - Update packages/blocks to use catalog: (from ^1.10.0) This update brings the latest Kumo UI components and potential RTL improvements. Custom CSS overrides in admin/src/styles.css remain in place for Kumo Sidebar RTL support. * fix(admin): override Kumo GroupLabel text alignment for RTL Add className="[&>span]:text-start" to all KumoSidebar.GroupLabel components to override Kumo library's hard-coded text-left class. Kumo v1.18.0 still uses non-RTL-aware classes in GroupLabel. This scoped override using Tailwind's selector syntax ensures group labels align correctly in both LTR and RTL layouts. Affected labels: Content, Manage, Admin, Plugins * fix(admin): add RTL support to Header component Add RTL-aware classes to Header component: - Sidebar.Trigger: Add rtl:rotate-180 to flip hamburger icon in RTL - ArrowSquareOut icon: Use me-1 (margin-inline-end) instead of mr-1 - Logout button: Use text-start instead of text-left for alignment All changes use Tailwind v4's logical properties and rtl: variant for automatic RTL layout support. * chore: add changeset for RTL support in admin UI * test(admin): update Widgets test selectors for RTL classes Update test selectors from `button.text-left` to `button.text-start` to match the RTL refactoring. The widget expand buttons now use direction-aware classes. * Revert "build: upgrade Kumo to v1.18.0 via catalog" This reverts commit 12eb2b982d15d513f67aef43ce43bb23a4f731d7. * refactor(admin): remove unsupported animation classes * feat(admin): replace hand-rolled dialog with @cloudflare/kumo primitive * fix(tests): update button locator class for older revision in revisions spec * test(admin): align UserDetail tests with Kumo Dialog primitive Adapts tests for the Dialog.Portal rendering model (renders outside screen.container) and the data-base-ui-inert overlay that blocks Playwright pointer events. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
64f90d1957 |
fix: repair invite flow — URL prefix + registration UI (fixes #67) (#542)
* fix: repair invite flow — URL prefix + registration UI + fix failing test Fixes the two compounding bugs in the user invite system (Issue #67): - Bug 1: Invite URL drops /_emdash/ prefix. Fixed by pointing to admin UI page. - Bug 2: No UI to complete registration. Added InviteAcceptPage with passkey registration. - Bug 3 (from PR #81 review): Fixed failing test assertion to match new URL path. Based on PR #81 by @SrikanthAlva with the requested test fix applied. Closes #67 * fix: address review comments — remove unused code, use ULID, useSearch, fix duplicate button, simplify email store * fix(auth): remove signup from public admin routes Making /_emdash/admin/signup a public server-side route allowed unauthenticated direct access. The signup page is only intended to be reached via client-side navigation from the login page. Made-with: Cursor --------- Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
10ebfe19b8 |
Fix autosave form reset bug (#302)
* Fix autosave form reset bug Autosave was invalidating the query cache after completion, which triggered a refetch of content data from the server. This refetch updated the item prop, causing a useEffect in ContentEditor to reset form state to the server values, overwriting unsaved user changes. The fix removes the queryClient.invalidateQueries() call from the autosaveMutation onSuccess handler. The local form state remains the source of truth during editing, and manual saves still properly invalidate and refresh the cache. Fixes #295 * Fix repeated autosave after successful autosave * Stabilize autosave status assertions * Patch autosave cache after autosave * style: format * Add autosave regression coverage * Fix autosave CI follow-ups --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com> |
||
|
|
e2f96aa74b |
fix(admin): use loadMessages in useLocale hook (#504)
* fix(admin): use loadMessages in useLocale hook
The client-side locale switcher had the same dynamic import issue —
raw `import(./${code}/messages.mjs)` fails when resolved through
Vite aliases. Reuse the glob-based loadMessages from index.ts.
* fix(admin): replace locale toggle buttons with kumo Select
Toggle buttons don't scale beyond a few locales. Use the kumo Select
dropdown instead. Add e2e tests for the language switcher.
* chore: add changeset
|
||
|
|
c70f66f7da |
fix(admin): detect redirect loops (#351)
* add selection rule for readable text selection contrast * fix: detect and prevent redirect loops * add changeset file * fix typecheck error * clean-up redundant e2e tests --------- Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
476cb3a585 |
fix(middleware): allow public access to search API at middleware layer (#424)
PR #107 removed the handler-level `requirePerm()` check from the search endpoints, but the auth middleware still returned 401 before the handlers ran because `/_emdash/api/search` was not in `PUBLIC_API_EXACT`. The handler-level changes therefore never executed for anonymous callers, and the shipped `LiveSearch` component (which fetches without credentials) silently showed "No results found" on every query. This change adds `/_emdash/api/search` to `PUBLIC_API_EXACT` so the middleware lets anonymous GET requests reach the handler. The query layer already hardcodes `status='published'`, so anonymous callers still only see published content. Admin endpoints (`/enable`, `/rebuild`, `/stats`, and `/suggest`) remain authenticated because they are not in the set. The existing E2E test `"search endpoint requires authentication"` asserted the buggy behavior and is replaced with two new tests: one verifying public access to `/_emdash/api/search`, and one verifying that `/stats` and `/enable` remain gated. Closes #104. Follows up on #107. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Matt Kane <mkane@cloudflare.com> |
||
|
|
5eeab91882 |
i18n: Fix new content always being created in English (#377)
* Fix content always created in English * formatting * Remove i18n from simple config * Improve router test to catch edge cases * Fix E2E test failing due to an outdated regex * Fix await-thenable lint error in router tests Locators returned by screen.getByRole() are not Thenables — awaiting them directly triggers the await-thenable lint rule. Remove the unnecessary await when assigning the locator to a variable; the locator is still usable for expect.element() and .element() calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Matt Kane <mkane@cloudflare.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d2114523a5 |
fix: passkeys behind TLS reverse proxy (#225)
* fix: passkeys behind TLS reverse proxy Add passkeyPublicOrigin and wire it through passkey routes so origin/rpId match the browser when dev runs behind nginx. Expose dev-only /_emdash/api/dev/passkey-url, add admin messaging for insecure WebAuthn contexts, nginx repro under demos/simple, and direct kysely dependency for the simple demo Node adapter bundle. Made-with: Cursor * docs: add passkeyPublicOrigin to configuration reference Adds the new passkeyPublicOrigin option and reverse proxy guidance to the public-facing configuration docs as requested in PR review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update tests and more docs * fix: add missing refresh-server-pat fixture and restore docs heading --------- Co-authored-by: Joseph Eftekhari <jdeftekhari@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
ca3c2b77e1 | Format | ||
|
|
43fcb9a131 | first commit |