Skip to content

Ember to React YOLO migration#28486

Draft
jonatansberg wants to merge 32 commits into
mainfrom
ember-to-react-migration
Draft

Ember to React YOLO migration#28486
jonatansberg wants to merge 32 commits into
mainfrom
ember-to-react-migration

Conversation

@jonatansberg

Copy link
Copy Markdown
Member

Fable

- ghost/admin has workspace:* deps on apps/* (e.g. @tryghost/admin-x-framework)
  and pnpm 11 strict-validates the workspace graph at install time
- compose.dev.yaml already mounts apps/ into the ghost-dev container for this
  reason, but the e2e worker containers were never given the same bind, so
  every dev-mode e2e run failed during global setup
- applied the same apps/ bind in the e2e ghost-manager
…tations

no ref

- groundwork for migrating the tag detail screen from Ember to React: the same
  Playwright suite (same page objects and selectors) will run against both
  implementations, gated by the tagDetailsX labs flag
- normalized page-object selectors to semantic locators and data-testid, adding
  the matching data-testid attributes to the Ember templates (kept the existing
  data-test-* attributes for Ember acceptance tests)
- extracted the tag editor tests into a shared, order-independent suite using
  factories with unique names, since per-file isolation shares DB state across
  tests in a file and the previous tests were order-dependent
- added missing coverage for the unsaved-changes guard and meta data round-trip
- opted list.test.ts into per-test isolation: it relies on pristine default
  content (single 'News' tag) and was order-dependent under per-file reuse
- added DEVIATIONS.md as a running log for the Ember-to-React migration
no ref

- gates the upcoming React implementation of the tag detail/new screen; the
  Ember implementation remains the default while the flag is off
- also lets the shared e2e suite run against both implementations
no ref

- dev-mode Ghost containers run a pnpm install check at boot which can take
  several minutes on slower machines; the 60x1s health-check window flipped
  containers to 'unhealthy' (and waitForReady's 120s default timed out) even
  though boots complete successfully shortly after
- waitForHealthy still bails out early if the container exits, so the wider
  window only extends the success path
no ref

- the sidebar rendered role=navigation with no accessible name; with the new
  React tag detail screen adding a semantic breadcrumb <nav>, the e2e
  SidebarPage locator (getByRole('navigation')) became ambiguous
- naming the landmark is the a11y-correct fix when multiple navigation
  landmarks exist, and the page object now targets it explicitly
no ref

- ports the Ember tag detail/new screen to React in apps/posts (where the tags
  list already lives), built with Shade + react-hook-form + zod: main fields,
  meta data / X card / Facebook card / code injection sections with previews,
  image uploads, auto-slug generation (incl. hash- prefix for internal tags),
  save states, delete confirmation and an unsaved-changes guard via
  react-router's useBlocker
- the admin shell routes /tags/new and /tags/:tagSlug through FlagGatedRoute:
  React when the tagDetailsX labs flag is on, EmberFallback otherwise
- the Ember tag route hands its URL to the react-fallback catch-all when the
  flag is on, so the hidden Ember app doesn't load data or register transition
  guards that fight React's navigation (this broke delete-then-navigate)
- added TagsResponseType to the state bridge's emberDataTypeMapping so React
  tag mutations keep the Ember store in sync
- extended the framework tags API with full snake_case fields, slug lookup and
  add/edit mutations; exported useBlocker; exported shade's Accordion
- the shared e2e suite now also runs against React (editor-react.test.ts);
  all 26 tests pass against both implementations with the same page objects
…ndings

no ref

Fixes from the /code-review + Codex passes on the tagDetailsX slice:
- visibility is derived from the name on save (renaming '#tag' to 'tag' now
  demotes it to public; the server only ever promotes)
- re-entrant submit guard (cmd+S key repeat could create duplicate tags;
  Ember used a drop-concurrency task)
- a save resolving after the user navigated away no longer yanks them back
  (guarded by an unmount ref that re-arms on mount for StrictMode)
- server validation messages surface in the save/delete error toasts
- slug generation uses @tryghost/string like Ember (transliteration parity;
  the hand-rolled version produced empty slugs for non-Latin names)
- bare hex accent colors are normalized with a leading # on blur
- leading-comma names are rejected client-side (matches server validation)
- useConfirmUnload guards browser unload like in-app navigation
- the unsaved-changes blocker reads live form state (no bypass ref) and lets
  in-flight saves complete without a spurious dialog
- the View button prefers the canonical URL (Ember parity)
- transient refetch errors no longer unmount the form as a 404
- tag mutations use updateQueries instead of invalidateQueries: invalidation
  refetched every loaded page of the tags list and its Ember-side handling
  (store.unloadAll) stripped tags out of loaded posts' embedded relationships
- tag routes no longer set allowInForceUpgrade: force-upgraded sites must not
  be able to manage tags via the React screen
- scoped useWatch in the accordion sections (code-injection keystrokes no
  longer re-render every preview) and deduplicated the X/Facebook card markup
- e2e: factory tags use unique names (faker department names collided in
  per-file environments), the Ember run pins tagDetailsX off so a future flag
  GA fails loudly, added a visibility-demotion test, and NAV_ITEMS URL
  patterns tolerate dev-mode's missing /ghost trailing slash
no ref

- groundwork for migrating the posts/pages list from Ember to React: the same
  suite will run against both implementations, gated by a postsListX labs flag
- covers the gaps the Ember acceptance tests had over e2e: status grouping,
  type/visibility/tag/order filters, URL-driven filters, multi-select with
  context-menu bulk actions (feature, add tag, unpublish, delete, duplicate)
  and the pages list
- added a createPageFactory (pages share the posts schema; only the API
  resource differs)
- added data-testids to the Ember context menu, bulk-action modals and status
  badges (kept existing data-test-* attributes for Ember acceptance tests)
- PostsPage gained row-selection/context-menu helpers and a PagesPage subclass;
  getPostByTitle now tolerates the star icon featured posts prepend to the
  heading's accessible name
no ref

- gates the upcoming React implementation of the posts and pages list screens;
  the Ember implementation remains the default while the flag is off
- lets the shared e2e list suite run against both implementations
no ref

- ports the Ember posts/pages list screens to React in apps/posts following
  the members-list architecture: three status sections (scheduled, drafts,
  published+sent) backed by infinite queries, Ember-compatible URL query
  params (type/visibility/author/tag/order), filter dropdowns, status badges,
  featured stars and editor links
- Ember-parity selection model (ctrl/meta toggle, shift ranges, invert via
  cmd+A, Escape clears) and right-click context menu with bulk actions
  (feature/unfeature, add tag, change access, unpublish, unschedule,
  duplicate, delete) as pure, unit-tested modules
- bulk operations go through new framework hooks (PUT /posts/bulk, DELETE
  /posts/?filter=, POST /posts/:id/copy) shared by posts and pages
- the admin shell routes /posts and /pages through FlagGatedRoute; the Ember
  routes stay active so URL query params survive, but their model hooks and
  templates are gated on the flag — hidden Ember markup would otherwise
  duplicate the React list's test ids and break shared-locator e2e
- the shared e2e suite now also runs against React (list-react.test.ts);
  all 26 tests pass against both implementations with the same page objects
- custom views UI and analytics columns land separately before flag GA
no ref

- custom views (saved filters) with Ember parity: default Drafts/Scheduled/
  Published views, active view name as the page title, Save as view / Edit
  current view buttons, a Shade dialog matching the shared e2e selectors
  (View name label, Ember's color swatch labels and validation messages),
  persisted in the shared_views setting so the admin sidebar updates live
- analytics columns on published rows gated by web_analytics_enabled and
  members_track_sources (visitor counts, opens/sent, member conversions)
  batched per loaded page via the stats endpoints
- fixed sidebar active-state for list views: React Router navigates with
  pushState which fires no hashchange, so the hidden Ember router's query
  params go stale and the Ember-bridge isRouteActive matching silently broke
  whenever React owned the navigation — custom view, Drafts/Scheduled/
  Published and Pages active states are now computed React-side from the URL
  (same approach the rest of the sidebar already used via useMatch)
- extracted the three custom-views e2e files into shared suites that run
  against Ember (flag pinned off) and React (postsListX on); save/edit view
  buttons in the Ember template gained aria-labels (their accessible name
  came from the inlined svg title in dev builds, breaking the locators)
- restored apps/posts eslint devDeps that pnpm dep changes had un-hoisted
- full posts/pages e2e set (list + custom views, both implementations):
  60/60 passing
…dings

no ref

Fixes from the /code-review + Codex passes on the postsListX slice:
- bulk mutations invalidate both posts and pages query caches at the framework
  layer (other cached filter combinations went stale for the whole session)
- bulk actions surface success/error toasts with real API messages; modals stay
  open on failure (errors were silently swallowed as unhandled rejections)
- change-access modal gained 'Specific tiers' with a tier picker and preselects
  the selection's current visibility (it silently flipped paid posts to public)
- add-tags modal and the tag filter search server-side and support inline tag
  creation (only the first 100 tags were reachable)
- context-menu parity: Unpublish hidden for sent email posts, feature/unfeature
  uses Ember's majority rule (selections that were mostly featured re-featured
  everything instead of unfeaturing)
- publish success modal restored on the list routes (the scheduled/published
  localStorage handoff from the publish flow went unconsumed and could leak a
  stale modal into the analytics screen)
- filter changes replace history entries (Ghost#11057 parity), Escape no longer
  wipes the selection while a modal or the context menu is open, 'Featured
  pages' restored for pages
- NQL hygiene: selection ids must be ObjectId hex, author/tag URL params must
  be slug-shaped, bulk select-all filters are scoped with type:post|page
- dedup: shared_views save/delete scaffold shared between members and posts
  views; view-filter equality and the color palette exported once from
  @tryghost/posts/api and consumed by the admin sidebar; stats count endpoints
  moved into the framework api; list rows memoized so selection clicks don't
  re-render every row
- e2e: the feature bulk action waits for the bulk response before filtering
  (race); Ember store sync mappings for PostsResponseType/PagesResponseType
  set to null (lists are React-owned while the flag is on; unloading posts
  could disturb records held by the still-Ember editor)
- documented pre-existing upstream issues found during review in DEVIATIONS.md
  (server-side bulk authorization gap, stats endpoint ownership, shared_views
  last-write-wins)
no ref

- gates the upcoming React implementation of the member detail screen and
  member activity; the Ember implementations remain the default while off
no ref

- groundwork for migrating the member detail screen and member activity from
  Ember to React: the same suites run against both implementations, gated by
  the memberDetailsX labs flag
- extracted the members-legacy detail tests (CRUD/validation, impersonation,
  disable-commenting, activity events) into order-independent suite factories
  with unique factory data, removing the per-test isolation requirement
- added missing coverage: unsaved-changes guard (stay/leave), newsletter
  subscription toggle round-trip, note round-trip, and a new members-activity
  suite (event listing, member filter, event-type exclusion)
- normalized fragile selectors to data-testid (members-back link, labels
  input) with matching testids added to the Ember templates
- members-activity feed only shows events created strictly before the page's
  load timestamp at second precision; the suite polls the events API with the
  same cursor filter before navigating to avoid same-second flakiness
- 56 tests passing against Ember (React wrappers pass against the ungated
  Ember screens until the implementation lands)
…etailsX

no ref

- ports the Ember member detail screen to React in apps/posts: identity
  sidebar (avatar, geolocation, last seen, signup info, attribution,
  engagement stats, comments-disabled indicator), form (name/email/labels
  with typeahead+create/note/newsletter toggles incl. email suppression
  re-enable), subscriptions section (tier cards, status badges, Stripe
  links, add/remove complimentary with expiry, cancel/continue), actions
  menu (impersonate with signin URL copy, sign out of all devices,
  disable/enable commenting, delete with optional Stripe cancellation),
  embedded activity feed, and Ember-parity save semantics
- ports members-activity: full event table with cursor-paginated infinite
  fetch, Ember-compatible member/excludedEvents query params, event-type
  filter, member context card; the event parsing helper is a complete pure
  port with unit tests
- extracts the unsaved-changes blocker + dialog into a shared module (the
  tag detail form now uses it, as promised in DEVIATIONS)
- framework: member edit/delete (with Stripe cancel param), signin URLs,
  session signout, suppression delete, subscription edit, and cursor-based
  member events hooks
- admin shell routes /members/new, /members/:memberId and /members-activity
  through FlagGatedRoute; the Ember member route hands over to react-fallback
  (it registers unsaved-changes guards) while members-activity is gated at
  the template level to preserve its query params
- the shared e2e suites pass 54/54 against both implementations
no ref

Fixes from the /code-review + Codex passes on the memberDetailsX slice:
- members-activity sanitizes its URL params before building NQL filters
  (member must be ObjectId hex, excludedEvents whitelisted against known
  event types — crafted params could widen the events query)
- the impersonate dialog refetches the signin URL on every open (the cached
  URL could be five minutes stale after an email change)
- member save invalidates the labels cache (labels created inline during a
  save were missing from typeaheads and filters for up to five minutes)
- the add-complimentary dialog disables confirm while a custom expiry date
  is empty/invalid (it silently degraded to a forever subscription)
- server validation errors map onto the offending form field inline (Ember
  parity) instead of only flashing a toast
- members-activity gained the member search picker (Ember parity — the only
  member-scoped entry point was a hand-crafted URL) and automatic infinite
  scroll via an observer sentinel alongside the Load more fallback
- documented in DEVIATIONS: force-upgrade handle removal, same-second cursor
  parity, email-preview-link deferral, flag-flip limitation
- full member dual suites + tags React regression: 67/67 passing
no ref

- groundwork for migrating the auth screens to React behind an authX flag:
  suite factories with Ember/React wrapper pairs, fully semantic locators
  plus flow-notification testids added to the Ember templates
- new coverage: signin validation errors, forgot-password errors, wrong 2FA
  code; 2FA and reset suites pin security.staffDeviceVerification via config
  env (a local config.local.json disabled it, silently breaking 2FA locally)
- reset wrappers use per-test isolation (completing a reset rotates the
  session and invalidates the per-file cached auth state); MailPit count
  assertions made baseline-relative
- documents the BetterAuth feasibility decision in DEVIATIONS.md: the React
  screens will call Ghost's existing session/authentication endpoints; full
  BetterAuth server adoption is a separate platform project (sourced
  assessment included)
The pnpm dev container was exposing a broader workspace at runtime than the Docker image installed, causing pnpm 11 to repair dependencies before starting. Narrowing the source mounts keeps the runtime graph aligned with the image install and disables script-time dependency repair for this dev container.
Dev-mode E2E worker containers use the same Ghost dev image as compose.dev.yaml, so they need the same narrowed backend workspace mounts and pnpm verifier override. This keeps the worker container runtime graph aligned with the image install instead of exposing ghost/admin.
no ref

- the admin-x-framework Post type now ships PostAuthor[] (added with the
  React posts list), which made the stats app's narrower local authors
  override incompatible and broke tsc on @tryghost/stats:build
- the override is redundant now that the framework type includes name
no ref

- ports signin, 2FA verify, password reset, signup, setup and signout to
  the React admin shell against Ghost's existing session/authentication
  endpoints (deliberate deviation from the BetterAuth instruction — see
  DEVIATIONS.md for the full justification)
- authX is exposed on the public site payload so both shells can read the
  flag before a session exists
- both Ember route bases park the hidden Ember app when the flag is on:
  AuthenticatedRoute's replaceLocation fallback wiped deep-link URLs and
  UnauthenticatedRoute's prohibitAuthentication rewrote the shared URL
  after the post-signin reload
- the post-signin deep-link redirect captures its target once (query
  refetches re-render the gate after the key is cleared) and navigates
  via location.replace, since router pushState fires no hashchange and
  would leave the parked Ember app showing an empty content area
- e2e auth suites run against both implementations with the same page
  objects (26 tests, flag off/on)
no ref

- review findings from the multi-angle code review + Codex adversarial
  pass on the authX slice:
- flag-off parity: the auth gates now reveal the Ember fallback as soon
  as the site payload says authX is off, instead of also waiting for the
  /users/me query to settle (pre-slice Ember never gated on it)
- signin redirects to /setup on fresh installs, restoring Ember's
  UnauthenticatedRoute setup-status check
- invalid or expired invite links explain themselves on the signup screen
  instead of silently bouncing to signin
- the API's minimum password length lives in one shared constant instead
  of three inline literals
no ref

- ports the editor screen (posts and pages) to the React admin shell:
  title + Koenig lexical body, autosave, publish/update/preview flows,
  settings menu (slug, publish date, tags, excerpt, feature image,
  delete) and unsaved-changes guards
- per the plan, state management lives in a discrete, independently
  testable state machine (apps/admin/src/editor/state/) — a pure
  transition(state, event) -> {state, effects} reducer covering scratch
  values, dirty detection, the save queue (manual supersedes background,
  latest-wins queueing, queue-drain completion), status transitions and
  leave decisions, with ~100 unit tests and no React/DOM/network imports
- Koenig is imported as workspace ESM (no UMD loader); the Ember
  lexical-editor route hands over via react-fallback when the flag is on
- new framework editor/slugs APIs (full-post payloads with Ember's
  ALL_POST_INCLUDES, 409 UpdateCollisionError, newsletter/email_segment
  publish params, slug generation)
- cross-shell navigations (publish complete, delete, editor backlink)
  use real location hash changes so a parked flag-off Ember list wakes;
  the shade share modal carries the publish-complete test hooks shared
  with Ember's complete step
- e2e: editor, publishing, publish-flow, post-preview and post-updates
  suites converted to shared dual-flag suites — 36 tests green against
  both implementations, plus 19-test flag-off baseline before the port
no ref

- review findings from the multi-angle code review + Codex adversarial
  pass on the editorX slice, all covered by new unit tests:
- failed publish/schedule saves disarm the publish intent so a later
  plain save can't publish unexpectedly; queued saves carry their full
  intent (saveType/publishedAt) instead of just a kind
- scratch edits made while a save is in flight survive the response
  resync and failure reverts; email extras are per-request
- slug generation races are token-guarded and slug blur commits scratch
  before the async sanitize, closing a leave-guard data-loss hole
- contributor/author access gates ported from Ember's edit route
- DST-gap time conversions verified as fixpoints, impossible calendar
  dates rejected; excerpt length validated before the publish flow opens
- the Ember bridge maps page model events so Ember-side page edits
  invalidate React page caches
no ref

- ports the remaining Ember-owned screens to the React admin shell:
  restore-posts (plus the localStorage local-revisions store, written by
  the React editor with Ember's exact schema so revisions are restorable
  from either shell), site preview, billing (/pro), explore and migrate
  iframe wrappers, and the home/dashboard redirects
- home/dashboard carry no labs flag: they are pure redirects with no UI;
  the home redirect is effect-based and guarded by the live hash so a
  navigation right after landing on "/" isn't clobbered (Ember's ran
  synchronously inside the route transition)
- removed the dead /launch, /mentions and /posts/analytics/:postId/
  mentions entries from EMBER_ROUTES and Ember's router; only the email
  debug screen and the (deliberately unported) designsandbox remain
  Ember-owned
- e2e: redirects, restore and site suites (dual wrappers where flagged)
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 08a1a484-2cb1-41a4-8a3a-08c32ba9f71d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ember-to-react-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

const trimmedQuery = query.trim();
const [debouncedQuery] = useDebounce(trimmedQuery, 300);
// Mirrors Ember's tagsManager.searchTagsTask filter, including the quote escaping
const safeTerm = debouncedQuery.replace(/'/g, '\\\'');
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebounce(search.trim(), 300);
// Mirrors Ember's tagsManager.searchTagsTask filter, including the quote escaping
const safeTerm = debouncedSearch.replace(/'/g, '\\\'');
no ref

- per review feedback on the migration so far:
- posts/pages list filters sit inline with the New button (right-aligned
  toolbar, wrapping below on narrow viewports like Ember)
- tag/member detail and members-activity share a PageCanvas matching
  Ember's gh-canvas metrics (max-width 1200, measured paddings), so
  content no longer jumps when switching implementations
- the editor's populated states restyled to Ember parity: header bar
  (green Publish, ghost Preview, breadcrumb, panel icon), 419px settings
  column, the publish/update flows' full designs (green headline, pill
  radios, gradient confirm), preview chrome, and exact gh-editor-title
  typography in a 740px column
- tag code injection now uses the admin-x-design-system CodeEditor
  (CodeMirror with HTML highlighting), reversing the slice-1 deviation
  per direction; the editor preview gained Ember's Email tab (rendered
  via a new email-previews framework hook) with newsletter and
  free/paid segment selection
- the React shell normalizes a slashless /ghost admin path at boot
  (Ember's location API used to), keeping hash-history URLs canonical
no ref

- per review feedback on the migration:
- the editor settings sidebar gained Ember's remaining PSM sections:
  post access/tiers, authors, template select, featured and
  show-title-and-feature-image toggles, and collapsible code injection
  (CodeMirror), meta data, X card and Facebook card sections — all
  flowing through the editor state machine (new PostSettings record,
  SETTINGS_CHANGED event, per-field resync that preserves in-flight
  edits) with Ember's role gates and serializer rules
- the settings menu module is lazy-loaded: its design-system/CodeMirror
  graph was loading eagerly and pushed the editor's first render from
  ~150ms to ~7s, which also broke the reload e2e budget
- posts/pages list rows match Ember: feature-image thumbnails (with
  placeholder), colored status lines and Ember's exact wording,
  gh-format-post-time dates, the pencil/stats hover button, and no
  status group headers (Ember has none)
- framework: Theme.templates typed as ThemeTemplate[] (was string[])
no ref

- ports the last functional Ember screen: the post email debug panel
  (batches, temporary/permanent recipient failures, analytics status
  with custom scheduling) behind the postDebugX flag, with new framework
  email hooks; only the Ember-only designsandbox remains, deliberately
- removed the boot-time /ghost path normalization: rewriting history
  state before the routers captured the location broke the post-signin
  redirect chain in slashless-serving environments; the sidebar e2e
  assertions are slash-tolerant instead (the slice-2 precedent)
- the deep-link signin e2e waits for the redirect back to the signin
  screen before interacting: the deep link is a same-document hash
  navigation and the form remounts when the shell bounces back — under
  load the fills landed in the old mount and the form submitted empty
- the current-user query now retries transient failures (network/5xx)
  while keeping 4xx definitive: this query decides authenticated vs
  signed-out for the whole shell, and one dropped request must not
  strand a logged-in user on the signin screen
- build-mode type fixes (editor email-preview null coercion, machine
  settings-resync cast, test resource literals)
- full admin e2e suite: 352 passed
no ref

- final-slice review pass (multi-angle + Codex adversarial), all fixes
  unit-tested:
- the migrate iframe's apiUrl reply is deferred until the integration
  key has loaded (it could send apiKey undefined); the explore screen
  validates postMessage origins by exact origin, not substring; the
  billing iframe receives owner details for non-owner force-upgrade
  sessions (Ember billing-service parity)
- local revisions now carry the full serialized post in Ember's field
  names (authors, feature image, visibility/tiers, template, meta/
  social/code-injection) and restore end-to-end; the pending throttled
  revision is flushed on editor unmount; corrupt stored entries are
  skipped instead of throwing
- the debug screen's permission redirect uses a real hash navigation
  (cross-shell safe) and the template default normalizes to null
- the post-updates e2e date assertion tolerates the midnight boundary
  between the host and container clocks
- DEVIATIONS.md final summary: migration end-state, flag-GA cleanup
  list, superseded notes
@github-actions

Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 27325881035 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

no ref

- the per-screen parity audit (AUDIT-REPORT.local.md) surfaced
  correctness bugs the dual e2e missed; all fixed with unit tests and
  verified live:
- new posts no longer persist a slug derived from the first few title
  keystrokes: draft saves regenerate the slug server-side whenever the
  title changed (Ember beforeSaveTask parity, uniqueness-incrementor
  aware), replacing the racy pending-slug mechanism
- the settings panel's date field no longer stamps published_at onto
  never-published drafts or rewrites seconds on published posts
- the title input no longer shows the literal "(Untitled)" the API
  payload uses for empty titles
- setup starts the owner onboarding checklist before booting the admin
  (fresh installs were skipping onboarding entirely)
- contributors get Ember's Preview + Save controls instead of the
  publish/unpublish buttons their role can't use
- breakout Koenig cards respect the open settings panel via Ember's
  --editor-sidebar-width mechanism, and the title shares the body's
  740px column (a leaked Ember textarea max-width capped it at 500px)
- the shared unsaved-changes blocker holds navigation until in-flight
  saves settle and surfaces failures (tag + member detail could lose
  edits silently mid-save)
- full admin e2e after the fixes: 352 passed
no ref

- ports the two remaining high-severity parity gaps from the audit:
- the publish flow gained Ember's email recipients controls (free/paid/
  specific-people with label and tier segment pills, Stripe-gated paid
  option, forceSpecific behavior), persisting through the publish save's
  email_segment param
- the editor canvas gained Ember's feature image above the title: add/
  replace/remove with hover overlay, alt text toggle and a Koenig-backed
  caption editor; alt and caption flow through the editor machine and
  local revisions; published posts defer the save to Update like Ember
- ports Ember's caption HTML normalization with a fix for Chrome's
  white-space shorthand expansion that silently broke the original check
no ref

- the publish flow's member-count query (which gates email availability)
  could read a cached zero from before members were added, wrongly
  disabling the email publish types until a manual refresh
- refetch the count whenever the flow opens, matching Ember's
  publishOptions.setup, which re-fetched it every time
- surfaced as an intermittent e2e failure on the email/scheduled
  publishing tests once the recipients selector added load to the flow
@github-actions

Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 27343208778 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants