Skip to content

refactor(admin): migrate admin app to React#1052

Open
Innei wants to merge 49 commits into
masterfrom
refactor/admin-react-migration
Open

refactor(admin): migrate admin app to React#1052
Innei wants to merge 49 commits into
masterfrom
refactor/admin-react-migration

Conversation

@Innei
Copy link
Copy Markdown
Member

@Innei Innei commented May 24, 2026

Summary

  • migrate the admin frontend runtime from Vue/Naive UI to React/React Router
  • consolidate the admin application under apps/admin/src/app with React providers, routes, shell, API clients, UI primitives, and page implementations
  • update Vite, TypeScript, workspace, documentation, and dependency metadata for the React application

Verification

  • pnpm -C apps/admin typecheck
  • pnpm -C apps/admin lint
  • pnpm -C apps/admin build

Notes

  • build succeeds with non-fatal lightningcss ::highlight warnings from rich-editor CSS
  • build also reports large chunk warnings after minification

@safedep
Copy link
Copy Markdown

safedep Bot commented May 24, 2026

⚠️ Scan Failed: Pull Request Too Large

This pull request exceeds GitHub's diff limits and cannot be scanned.

GitHub Limits:

  • Maximum 300 files per diff
  • Maximum 1 MB total diff size

Recommendations:

  • Split this PR into smaller, focused changes
  • Review critical dependency changes manually
  • Contact your team if this is blocking your workflow
    This report is generated by SafeDep Github App

Innei added 2 commits May 25, 2026 00:19
- Added ejs@5.0.2 and react-resizable-panels@4.11.1 to the project.
- Introduced code-inspector-plugin@1.5.1 and its related packages.
- Updated dotenv to version 16.6.1 and chalk to version 4.1.1.
- Added new Vue compiler packages: @vue/compiler-core@3.5.34, @vue/compiler-dom@3.5.34, and @vue/shared@3.5.34.
- Updated various dependencies to their latest versions for improved performance and security.

Signed-off-by: Innei <tukon479@gmail.com>
…kage path

Signed-off-by: Innei <tukon479@gmail.com>
Comment thread apps/admin/src/api/http.ts Fixed
Move all submodules (api, constants, hooks, i18n, models, rich-editor,
socket, ui, utils, views) and top-level files (providers, query-client,
routes, shell, theme) from src/app/ up to src/. Rewrite ~/app/X imports
to ~/X, fix relative ./app/ refs in App.tsx, and update tsconfig include.
import { API_URL } from '~/constants/env'
import { SESSION_WITH_LOGIN } from '~/constants/keys'

const requestUuid = createRequestUuid()
Innei added 23 commits May 25, 2026 20:48
- add reusable Drawer component with motion-driven slide/fade animations and directional shadow
- align drawer header height to page header (h-16)
- migrate ContentSettingsDrawer and PageSettingsDrawer to shared Drawer
- extract draft-status-tag, header-back-button, and tag UI primitives
Introduce ui/datetime-picker.tsx with Base UI Popover trigger, decade/month/day views, time grid, and datetime-local string IO.
Unify post/note/page rendering into a single canvas. Replace the form-style
header (separate title, slug, subtitle, AI, format-toggle fields) with:

- Top meta strip (opacity-60 default, full opacity on hover) showing
  draft/save/published status as a colored dot + relative-time string,
  with ghost icon buttons for format swap and AI generate.
- Title input rendered as plain typography (text-3xl, no border/background).
- Slug surfaced as a hover-revealed mono pill below the title; clicking it
  opens a Base UI popover with the slug input (Enter preventDefault to
  avoid form submission), optional copy-URL action.
- Subtitle slot per kind: page shows always-visible subtitle input, post
  exposes a hover-revealed summary input, note hides the slot.
- Body container collapses both kind branches to max-w-[80rem], with
  --rc-max-width: 'none' forced on the editor surface so it fills the
  wider canvas instead of capping at the haklex default 700px.

Pass mainClassName="flex flex-col" to ContentLayout so the inner Scroll's
flex-1 takes a definite height; this fixes a regression where the editor's
overscroll-contain prevented wheel events from reaching the panel-level
scroller.

Spec saved at docs/superpowers/specs/2026-05-26-write-page-editing-area-redesign.md.
…itor toolbar

Upgrade @haklex/rich-plugin-toolbar to 0.16.0 to consume the newly exposed
abstraction layer (useToolbarState hook, applyBlockType/applyFontFamily
action helpers, FONT_FAMILIES/BLOCK_TYPE_LABELS constants, low-level
ToolbarButton/Dropdown/Separator), then build a project-styled toolbar in
rich-editor/components/EditorToolbar.tsx.

The new toolbar matches the rest of the admin (neutral palette, ghost icon
buttons, Base UI Menu dropdowns) and covers font family, heading dropdown,
undo/redo, bold/italic/underline/strike/code, highlight, spoiler, lists,
four alignment buttons, and dynamic insert items (first 5 inline plus an
overflow dropdown for the rest). Mounted via the `header` slot of ShiroEditor
across ReactEditorPane, NestedDocDialogEditor, and ShiroEditorBridge; the
upstream stylesheet is no longer imported.
Introduce `present(Component, props, options?)` that returns a
PromiseLike `ModalHandle` ({ close, dismiss, update, then }) so any
caller — hook, event handler, async fn — can mount a dialog without
threading open state through JSX. Inside the body, `useModal()` exposes
the same handle.

ModalRoot subscribes a module-level stack store via useSyncExternalStore
and renders each instance through the existing `<Modal>` shell. It
enforces top-only dismissal (Base UI does not stack sibling dialogs),
cancels backdrop/ESC for non-top and non-dismissable modals via
eventDetails.cancel(), and uses a 10-step z-index stride so a modal's
descendants (popovers, nested dialogs) stay below the next modal.

Modal gains optional onOpenChange and onExitComplete props (additive;
existing onClose callsites unchanged). `onClose` is now optional.

Migrate DraftRecoveryDialog: drop onClose and the outer <Modal> wrap;
caller drives it via useEffect → present, with cleanup-dismiss to handle
route changes. publishedContent is now memoised to keep effect deps
stable.

Spec: docs/superpowers/specs/2026-05-26-imperative-modal-design.md
Below the lg breakpoint, route the aside slot into a BottomSheet so
ContentLayoutSlot consumers continue to portal unchanged while phones
get a native-feeling sheet UI. Desktop keeps the existing resizable
PanelGroup unchanged.
Decompose 36 admin route view files from monolithic implementations
(some 2000-4700 lines) into thin route skeletons that re-export from
feature-scoped directories under apps/admin/src/features/<domain>/.

Each feature now follows a routes/components/types/utils/constants
boundary, with RouteViewContent as a thin orchestration layer:

- snippets 1974 -> 390 lines
- enrichment 1477 -> 397
- analyze 1287 -> 588
- dashboard 1122 -> 484
- comments 994 -> 393
- projects 957 -> 194
- webhooks 939 -> 200
- recently 867 -> 230
- cron 821 -> 305
- friends 806 -> 298
- setup 620 -> 120

Plus prior splits for settings, ai, posts, notes, pages, categories,
topics, drafts, comments, says, recently, files, write, backup,
markdown, search-index, subscribe, templates, readers, auth, debug.

Route registration in src/routes.tsx is unchanged; public behavior
preserved. Type-only declarations live in types/, query keys and
option lists in constants.ts, pure transformations in utils/, and
each non-trivial component in its own file.

Validated via typecheck, lint, and production build.
- formatters/i18n type signatures accept null/undefined
- draft-status-tag falls back to createdAt when updatedAt missing
- extract DraftHintBanner from write page
- minor spec doc table formatting
- New src/ui/codemirror/ module ported from master Vue editor:
  - Core: CodeMirrorEditor, useCodeMirror, editor-store (external store via useSyncExternalStore)
  - WYSIWYG block widgets (heading/list/image/codeblock/blockquote/details/math/...)
  - Floating selection toolbar + slash command menu
  - Drag/paste image upload via filesApi
  - Theme + font autosync via useThemeMode and localStorage settings
- Wire CodeMirrorEditor into WriteRouteViewsContent markdown branch, add ImageDropZone
- Drop unused post "一句话简介" inline subtitle input (summary still editable in meta panel)
- Aside Scroll grids use grid-cols-[minmax(0,1fr)] to prevent intrinsic-content overflow
- Related-reading section switches from checkbox list to SelectField + removable chips
- Checkbox indicator no longer keepMounted (was showing stale check icon in dark mode)
…ure widgets

- src/ui/ now contains only four category dirs: primitives, feedback, layout, data
- forked editor packages move to src/vendor/{rich-editor,codemirror}
- business-coupled widgets move into their owning features (drafts, snippets,
  analyze) or features/_shared/components/ when used by 2+ features
- hooks/utils/constants files relocate out of ui/ into existing top-level dirs
- ~178 importers + intra-ui refs updated; design spec added under docs/
Innei added 14 commits May 26, 2026 22:44
Replace the 850-line manual route + sidebar registry with a Vite plugin
that scans `apps/admin/src/views/` for convention files and emits a
`virtual:admin-routes` module consumed by routes.tsx, App.tsx, and the
sidebar. Each route lives in a `page.tsx` (lazy) or `page.sync.tsx`
(eager) and exports `metadata` via `defineMetadata({...})`. Section
grouping is expressed with `(name)` route-group folders plus per-section
`meta.ts`. Static + functional redirects move to `views/redirects.ts`.

URL surface is preserved end-to-end; URLs that share prefixes like
`/extra-features/*` keep the prefix inside their parens-grouped folder
to avoid behavioural changes.
Spec covers a two-line row layout (title row + meta row), persistent
dimmed action icons that brighten on hover (no layout shift), and a
new ~/ui/overlay/context-menu primitive with singleton imperative
store + render-prop ContextMenuTrigger, modelled after the existing
modal-imperative pattern.
- Restructure ContentEntryListItem to a two-line layout (title + meta)
  with persistent dimmed action icons that brighten on hover. No layout
  shift, no inline-edit widgets.
- Add singleton imperative ContextMenu primitive in ~/ui/overlay/context-menu
  modelled after lobe-ui: type-discriminated items (item / checkbox /
  submenu / divider / group), cloneElement Trigger with data-popup-open
  state, pointer-tracking store, ContextMenuHost mounted in providers.
- Add FloatLayerProvider + useFloatLayerContainer so menus Portal to a
  known top-level container, escaping any ancestor stacking context.
- Add monotonic z-index manager (useLayerZIndex) so the latest opened
  layer always sits on top — fixes submenus being trapped beneath their
  parent positioner.
- Extract menu class strings to ~/ui/overlay/menu-styles for sharing
  with the future DropdownMenu primitive.
- Drop NoteRow inline mood/weather InlineTextEdit; both move to a
  present()-driven NoteMetaEditModal triggered from the context menu.
- buildPostMenuItems / buildNoteMenuItems compose menu items per row.

PostsRouteViewContent gains a pin mutation and the row category list is
remapped to { id, name }. NoteMetadataUpdate gains bookmark.
- FocusScope primitive (refcounted) tracks the active interaction area via
  global pointerdown/focusin; sidebar + each list view declare their scope.
- useListSelection / useListShortcuts: single-source row selection (single /
  toggle / range) and Backspace/Enter/$mod+Enter actions gated on scope.
- useScopeArrowNav: J/K + Arrow + Home/End move focus through visible items
  in the active scope, with checkVisibility-aware filtering for sidebar
  collapse. onItemFocus callback mirrors cursor into selection state.
- Posts/notes row actions extracted into a ListAction registry so the
  right-click menu and shortcut bindings share a single source.
- Row visuals: Vercel-style neutral selection palette, no transition-colors,
  data-id + tabIndex on the article so the cursor focus and cmd/shift
  modifier clicks behave consistently.
Adopts lobe-chat's slice pattern (externalized action class, flattenActions,
subscribeWithSelector + shallow). Each store gets initial-state / action /
store / index split into its own folder.

- focus-scope, context-menu, modal-imperative, codemirror/editor-store →
  Zustand, with imperative wrappers preserved so callers like present.ts
  and the ContextMenu trigger keep their existing API.
- theme, codemirror/editor-setting → Zustand + persist middleware. The
  hand-rolled localStorage + custom-event broadcasts are gone; the matchMedia
  listener in theme.ts stays on useSyncExternalStore (DOM source).
- codemirror/image-popover-state → Jotai atom (small, transient).
- Add src/store/{types,utils/flatten-actions}.ts shared by every slice.

useSyncExternalStore now only remains in theme.ts for matchMedia.
Left over from the Zustand migration in b419ed9 — the new
editor-store/ folder fully replaces it.
Search now spans the available row width with no artificial cap; filter,
sort, refresh, and selection collapse into a single right cluster. The
selection control keeps a consistent visual register: a bare checkbox when
empty, a neutral count chip + danger-tinted bulk-action chip when populated,
replacing the previous inverted black pill.
…nslate()

Move literal UI strings across all admin features into en-US/zh-CN
resource bundles and route them through the new `~/i18n/translate`
helper, enabling runtime locale switching via UI_LOCALE_STORAGE_KEY.
Move dashboard section spacing from root gap-6 to per-block mt-6 so each
block owns its own rhythm. Soften the stats grid hairline color and pad
the trailing empty tracks with panel-bg spacers to avoid the dark color
patch on incomplete rows.
…ass name composition

docs: add design document for List Focus Scope Abstraction & Master-Detail Migration

refactor: update package.json scripts to use pnpm for admin app

chore: remove turbo.json configuration and related dependencies from pnpm-lock.yaml
Signed-off-by: Innei <tukon479@gmail.com>
- Replaced TopicFormDialog with TopicFormModal for better modal handling.
- Updated TopicDetail to use presentAddNotesToTopic for adding notes.
- Removed WebhookEditorDialog and implemented WebhookEditorModal for webhook editing.
- Adjusted TopicsRouteViewContent and WebhooksRouteViewContent to utilize new modal components.
- Enhanced MasterDetailLayout to support pixel-based resizing for list components.
- Cleaned up unused code and improved overall structure for better maintainability.

Signed-off-by: Innei <tukon479@gmail.com>
- Introduced new components for file management: FileThumbnail, FileListRow, FileDetailPane, UploadDropOverlay, and UploadProgressDock.
- Implemented hooks for file uploading (useFileUploader) and file searching (useFileSearch).
- Refactored existing routes to use the new components, improving code organization and maintainability.
- Added utility functions for formatting bytes and validating color previews.
- Updated constants and adapters for file types and statuses.
- Enhanced UI with improved search functionality and upload overlays.
- Updated translations for new UI elements and messages.
- Documented the redesign goals, structure, and migration plan in a new design spec.

Signed-off-by: Innei <tukon479@gmail.com>
- Replace top-level email/markdown tabs with MasterDetailLayout slim rail
- Add view toggle (Split / Code / Preview) in detail pane header
- Move sample props from static aside to editable JSON popover with live re-render
- Register Monaco ejs-html language + completion provider sourced from props keys
- Switch CodeEditor primitive to GitHub light/dark themes following app theme
- Add Test SMTP button hooked into /health/email/test
- Replace window.confirm with confirmDialog modal for reset
- Provide admin-side fallback demo props per template type so previews stay
  resilient when backend response is missing keys (e.g. newsletter detail_link)
],

comment: [
[/-->/, 'comment.html', '@pop'],
Innei added 8 commits May 28, 2026 02:46
…-grouped master-detail

- Drop the legacy AiRouteViewContent six-tab wrapper for these three routes;
  each /ai/* page now self-mounts AppPage + PageHeader + MasterDetailLayout.
- Share a generic ArticleGroupedRouteView engine driven by a per-surface
  ArticleGroupedConfig<TItem>; three thin route wrappers (Summary / Translation
  / Insights) supply queries, item shape, drawer body, and extra actions.
- List pane: borderless search + infinite scroll + neutral type badges; drops
  the noisy "raw article id" row footer. Both article and item rows use
  FocusScope + useListKeyboard + ListRow with proper j/k highlight + click sync.
- Detail pane: master-style article link + divider + section title + item rows;
  trash on hover; mobile back button.
- Edit + generate flows live in a ContentLayout split (resizable + collapsible)
  inside the detail slot, defaulting to a 70% editor pane.
- Embedded editors: CodeMirror for summary / insights / translation text;
  Lexical via a lazy-loaded LexicalEmbeddedEditor (with NestedDoc providers and
  nestedDocEditNodes) when the translation contentFormat is "lexical".
- PageHeader: new `iconOnly` flag on button actions so refresh stays square on
  desktop without falling back to `kind: "custom"`.
- ContentLayout: new optional `mainMinSize` prop so consumers can carve out
  more room for the aside.
- Docs: `.claude/skills/master-detail-list-keyboard/` codifies the j/k/click/
  drawer-sync recipe so the bug doesn't recur. Spec lives under
  `docs/superpowers/specs/2026-05-28-ai-article-grouped-redesign-design.md`.
…tries

- Delete /ai/slug-backfill route, its surface (SlugBackfillSurface), the
  WriterGeneratePanel helper, the slug-backfill API functions, and all
  related i18n keys. The `AITaskType.SlugBackfill` enum value and the task
  type label are kept so historical tasks still render in /ai/tasks.
- Drop the slug tab from `aiSurfaceTabs`, the slug branch from
  `getInitialAiSurface`, and the 'slug' variant from `AiSurface`.
- Migrate /ai/translation-entries off the legacy AiRouteViewContent tab
  wrapper into a self-contained AiTranslationEntriesRouteView modelled on
  the /posts list shell: FocusScope + ContentListHeader + a custom toolbar
  strip (keyPath select / lang input / refresh) + Scroll table + pagination.
  URL-syncs page/keyPath/lang. The old TranslationEntriesSurface and its
  AiRouteViewContent branch are removed.
- AI tasks: split monolithic surfaces into TaskListPane, TaskTimeline, TaskLogsBlock, TaskFilterChips, CollapsibleSection
- Analyze: extract page into blocks/ (metrics, content, composition, trend, paths, rank, activity, records, actions); add recharts-backed chart primitives
- Markdown: split into export/ and import/ panels with new MarkdownViewTabs
- Drop legacy /extra-features placeholder views; redirect old paths to flat routes
- i18n: add zh/en strings for AI logs, timeline, metadata, analyze time range
- Drop boxed Panel borders on settings detail; introduce flat SettingsSection (title + description + optional actions + dirty dot).
- Lift dirty-form action bar to the persistent detail header via a context setter to avoid layout shift; replace per-section Save with single Save-all / Discard.
- Rework /setting/account into a single-column layout; Token and Passkey lists now open via Drawer instead of a nested sub master-detail.
- Restructure AI settings around features: Providers list (row + edit Drawer) + Summary / Insights / Translation feature blocks + Other models, dropping the standalone "Model assignments" section.
- Move TestAiReview, ChangePassword, CreateToken, MetaPreset modals to the imperative present() API; drop legacy declarative Modal from SettingsPrimitives.
- Unify ConfigFieldEditor to a 2-column grid (label / control) with consistent baseline; render switches via headless Toggle so they align with input rows.
- Flatten Switch primitive (no default border; opt-in via bordered).
- Settings nav becomes a FocusScope with useScopeArrowNav for j/k navigation; active style switched from primary blue to neutral.
…and metadata popover

- SnippetList: collapsible folder groups, compact rows with colored type icons,
  hover external link + delete actions, client-side search
- SnippetsRouteViewContent: lazy per-group fetch via getSnippetGroups +
  getGroupSnippets, FocusScope + useListKeyboard (Case A), header overflow
  menu for Import/Update Deps, search input replaces type/group selects
- SnippetEditor: editor-primary layout — CodeEditor fills body, metadata
  fields move to new SnippetMetaPopover (Settings popover) with sectioned
  rhythm, iconOnly header actions
- SnippetPrimitives.Field: switch label+space-y-1 to div+grid gap-1.5 for
  consistent baseline with TextInput's labeled variant
- i18n: add snippets.list.{searchPlaceholder,emptyGroup,noResults,openExternal},
  snippets.editor.action.settings, snippets.filter.groupAria
- Drop unused snippetPageSize, readSnippetTypeFilter, groupSnippetList
- Sidebar header: brand (favicon + "Mx Space") left, avatar-only popover trigger right; theme + locale preferences moved inside the user popover and the persistent footer strip removed. Right-clicking the brand opens a product context menu (version + GitHub).
- Shell header height: APP_SHELL_HEADER_HEIGHT_CLASS h-16 -> h-14 (and matching 4rem -> 3.5rem CSS value).
- Page-header titles: unify h2 inside h-14 page headers to text-lg font-semibold across _shared/ContentListHeader and every feature route/detail pane.
- Topics: remove the inline icon prompt button from TopicDetail header; add an upload affordance next to the icon URL field in TopicFormModal that calls uploadFile(file, 'icon').
…n review

- migrate blurhash → thumbhash across models, api, files feature, and rich-editor types; replace blurhash decoder in FileThumbnail with thumbHashToDataURL
- add translation review section to AI settings (enableTranslationReview, translationReviewModel, translationReviewScoreThreshold) with en/zh strings

Ports master commits 980fe52, 24aadd8, and c746c33 to the React branch.
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