feat: improve admin build (#14485)

#### PR Dependency Tree


* **PR #14485** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Admin static assets now served under /admin for self-hosted installs
  * CLI is directly executable from the command line
  * Build tooling supports a configurable self-hosted public path
  * Updated admin package script for adding UI components
* Added a PostCSS dependency and plugin to the build toolchain for admin
builds

* **Style**
* Switched queue module to a local queuedash stylesheet, added queuedash
Tailwind layer, and scoped queuedash styles for the admin UI

* **Bug Fixes**
  * Improved error propagation in the Electron renderer
* Migration compatibility to repair a legacy checksum during native
storage upgrades

* **Tests**
  * Added tests covering the migration repair flow
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-21 23:25:05 +08:00
committed by GitHub
parent 0de1bd0da8
commit 2414aa5848
18 changed files with 397 additions and 53 deletions
@@ -1,19 +1,98 @@
import '@queuedash/ui/dist/styles.css';
import './queue.css';
import './queuedash.css';
import { QueueDashApp } from '@queuedash/ui';
import { useEffect } from 'react';
import { Header } from '../header';
const QUEUEDASH_SCOPE_CLASS = 'affine-queuedash';
const PORTAL_CONTENT_SELECTOR =
'.react-aria-ModalOverlay, .react-aria-Menu, [data-rac][data-placement][data-trigger]';
export function QueuePage() {
useEffect(() => {
const marked = new Set<HTMLElement>();
const markScopeRoot = (el: Element) => {
if (!(el instanceof HTMLElement)) {
return;
}
if (el.classList.contains(QUEUEDASH_SCOPE_CLASS)) {
return;
}
el.classList.add(QUEUEDASH_SCOPE_CLASS);
marked.add(el);
};
const isPortalContent = (el: Element) => {
return (
el.matches(PORTAL_CONTENT_SELECTOR) ||
!!el.querySelector(PORTAL_CONTENT_SELECTOR)
);
};
const markIfPortalRoot = (el: Element) => {
if (!isPortalContent(el)) {
return;
}
markScopeRoot(el);
};
const getBodyChildRoot = (el: Element) => {
let current: Element | null = el;
while (
current?.parentElement &&
current.parentElement !== document.body
) {
current = current.parentElement;
}
return current?.parentElement === document.body ? current : null;
};
Array.from(document.body.children).forEach(child => {
if (child.id === 'app') {
return;
}
markIfPortalRoot(child);
});
const observer = new MutationObserver(mutations => {
const appRoot = document.getElementById('app');
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof Element)) {
continue;
}
const root = getBodyChildRoot(node) ?? node;
if (appRoot && root === appRoot) {
continue;
}
markIfPortalRoot(root);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
return () => {
observer.disconnect();
marked.forEach(el => el.classList.remove(QUEUEDASH_SCOPE_CLASS));
};
}, []);
return (
<div className="h-dvh flex-1 flex-col flex overflow-hidden">
<Header title="Queue" />
<div className="flex-1 overflow-hidden">
<QueueDashApp
apiUrl={`${environment.subPath}/api/queue/trpc`}
basename="/admin/queue"
/>
<div className={`${QUEUEDASH_SCOPE_CLASS} h-full`}>
<QueueDashApp
apiUrl={`${environment.subPath}/api/queue/trpc`}
basename="/admin/queue"
/>
</div>
</div>
</div>
);
@@ -1,5 +0,0 @@
/* Scoped queuedash modal alignment */
.react-aria-ModalOverlay section[role='dialog'] {
transform: unset;
}
@@ -0,0 +1,14 @@
@import '@queuedash/ui/dist/styles.css' layer(queuedash);
/*
* QueueDash UI is built with Tailwind v3 (translate via `transform`), while AFFiNE Admin
* uses Tailwind v4 (translate via the individual `translate` property). When QueueDash
* overlays are portaled to `document.body`, both utility sets can apply at once and
* result in double transforms (mis-centered dialogs, etc). Reset individual transform
* properties within the queuedash scope so Tailwind v3 styles win.
*/
:where(.affine-queuedash) * {
translate: none;
rotate: none;
scale: none;
}