diff --git a/.agents/skills/macro-private-maintainer/SKILL.md b/.agents/skills/macro-private-maintainer/SKILL.md index 66eb7089b43..104a8acebad 100644 --- a/.agents/skills/macro-private-maintainer/SKILL.md +++ b/.agents/skills/macro-private-maintainer/SKILL.md @@ -78,6 +78,11 @@ description: Master orchestration skill for maintaining, auditing, developing, a 4. **修改优先级原则**:配置 > 环境变量 > Adapter 替换 > 依赖注入 > 反代 Proxy > 小范围 Patch > 修改 Domain。 5. **凭据安全红线**:严禁在 Git 追踪的文件中硬编码真实服务器 IP、私钥、OAuth Secret 或 API Key。 6. **UI 设计系统一致性**:前端二开必须复用 Macro 官方 `@ui`、Kobalte primitives、Theme 语义 Token、既有字号与动效语言;禁止建立平行组件库、私有颜色体系、任意字号或无障碍不受控的自定义交互。 +7. **上游/二开归因先行**:处理 CI、lint、typecheck、构建失败或 warning 前,先用 `git diff upstream/main -- `、`git show upstream/main:`、现有 workflow/just 脚本确认问题来源。结论必须区分:`上游已有`、`二开新增`、`二开触发上游隐患`。 +8. **上游非阻断问题不主动改**:若 lint / type / test 输出来自 `upstream/main` 已存在的问题,且不影响当前构建、CI 门禁、生产运行或本次二开目标,只记录来源与风险,不为“清爽”而修改上游代码。避免把私有 fork 变成无关风格修复分支,增加后续 upstream merge 成本。 +9. **二开代码必须贴合上游规范**:凡是本 fork 新增或本次触碰的二开代码,必须按上游现有目录边界、类型模型、query/service-client 分层、UI 组件规范、格式化与 lint 规则实现。若二开触发 warning/error,优先通过对齐上游模式修复;不要靠禁用规则、扩大类型、粗暴 cast、复制业务逻辑或改原始上游脚本来绕过。 +10. **上游原生运维入口优先**:遇到生产/本地环境状态漂移、服务初始化顺序、外部系统配置缺失、IaC 未落地、数据库/FusionAuth/LocalStack/OpenSearch/Redis/Kafka 等运行时状态不一致时,先查仓库原生脚本、Just recipes、Pulumi/Terraform/IaC 栈、Docker Compose、迁移与 README,再判断是否为“脚本未执行 / import 未完成 / reconcile 未覆盖”。禁止先写新的旁路补丁、手工 curl 脚本或业务代码兜底来掩盖漂移。 +11. **优先贴近 upstream 处理方式**:凡是 Macro 上游已有部署、初始化、导入、同步、回填、修复、seed、doctor、drift check、reconcile 等机制,优先复用或补齐调用路径;只有确认上游没有覆盖当前私有化场景时,才新增最小私有化封装,并明确标注原因与边界。 --- diff --git a/.agents/skills/macro-private-maintainer/skills/macro-local-environment/SKILL.md b/.agents/skills/macro-private-maintainer/skills/macro-local-environment/SKILL.md index 31cdc2c35b3..b4b3d90cde2 100644 --- a/.agents/skills/macro-private-maintainer/skills/macro-local-environment/SKILL.md +++ b/.agents/skills/macro-private-maintainer/skills/macro-local-environment/SKILL.md @@ -128,6 +128,8 @@ just stack down - Do not treat local DEV success as production or CI parity. - Use CodeGraph for symbol/call relationship analysis when `.codegraph/` exists. - Use Cargo/Nix/Compose graphs for build dependency analysis instead of guessing from filenames. +- For runtime state drift such as missing FusionAuth IdPs, stale LocalStack resources, missing queues, stale Docker volumes, missing seeded roles, or service config that exists in code but not in the running environment, inspect upstream-native repo mechanisms first: `just` recipes, `xtask_local`, Pulumi stacks, Docker Compose, migrations, seed tools, README runbooks, and existing doctor/status commands. Prefer restoring the intended upstream/IaC reconcile path over adding one-off curl patches or business-code fallbacks. +- For CI parity checks, distinguish upstream-existing warnings from fork-introduced failures before editing. If a warning exists on `upstream/main` and does not fail the current gate, report it without changing upstream code. Fix fork-introduced errors/warnings by following the upstream file's existing patterns and boundaries. ## References diff --git a/.fork/customizations.yml b/.fork/customizations.yml index 44041186c07..ed03dd864c0 100644 --- a/.fork/customizations.yml +++ b/.fork/customizations.yml @@ -42,6 +42,8 @@ customizations: - crates/macro_db_client/** - crates/macro_db_migrator/** - self-host/init/** + - apps/web/src/features/auth/EmailForm.tsx + - apps/web/src/features/auth/Login.tsx tests: - conventions - auth-rust diff --git a/.github/workflows/validate-service-cache.yml b/.github/workflows/validate-service-cache.yml index 30844ab7ecf..5b6d965f0df 100644 --- a/.github/workflows/validate-service-cache.yml +++ b/.github/workflows/validate-service-cache.yml @@ -1,9 +1,6 @@ name: Validate service cache planner on: - push: - branches: - - build/service-cache-20260906 pull_request: branches: - main diff --git a/apps/web/src/components/app/app-sidebar/sidebar.tsx b/apps/web/src/components/app/app-sidebar/sidebar.tsx index e4d36ccd9ae..46ed8ce1629 100644 --- a/apps/web/src/components/app/app-sidebar/sidebar.tsx +++ b/apps/web/src/components/app/app-sidebar/sidebar.tsx @@ -167,7 +167,7 @@ const DEFAULT_TRY_VISIBILITY: TryItemVisibility = { const markdownDocumentsQuery = buildDocumentTypeQuery(['doc-markdown']); -const SIDEBAR_LINKS = [ +const SIDEBAR_LINKS: SidebarItem[] = [ { id: 'inbox', label: 'Inbox', @@ -234,7 +234,7 @@ const SIDEBAR_LINKS = [ hotkey: 'c', hotkeyToken: TOKENS.sidebar.goTo.channels, }, -] satisfies SidebarItem[]; +]; export type SidebarState = 'hidden' | 'expanded' | 'slim'; @@ -1373,9 +1373,9 @@ export const AppSidebar = (props: AppSidebarProps) => { .map((id) => findLink(id)) .filter((link): link is SidebarItem => link !== undefined) .map((link) => ({ + id: link.id as SidebarSectionLinkId, label: link.label, checked: sectionVisibility()[link.id as SidebarSectionLinkId] ?? true, - onToggle: () => toggleSection(link.id as SidebarSectionLinkId), })); const tryItems = createMemo(() => { diff --git a/apps/web/src/components/app/sidebar-next/footer-actions.tsx b/apps/web/src/components/app/sidebar-next/footer-actions.tsx index 60d4d4b1e45..fcfee5cd8fc 100644 --- a/apps/web/src/components/app/sidebar-next/footer-actions.tsx +++ b/apps/web/src/components/app/sidebar-next/footer-actions.tsx @@ -2,10 +2,6 @@ import { runCreateAction } from '@app/features/command/Launcher'; import { globalSplitManager } from '@app/signal/splitLayout'; import { CALENDAR_BLOCK_ID } from '@block-calendar/types'; import { SidebarSettingsWidget } from '@components/app/app-sidebar/sidebar'; -import { - enableChatV3Agents, - isFeatureEnabled, -} from '@core/constant/featureFlags'; import { type SettingsTab, useSettingsState, @@ -68,13 +64,10 @@ export const FooterActions = (props: { // The two creatables both bind `a` and are mutually exclusive on the // agents flag, so pick the one that is actually registered. // `shouldInsert` is what `createBlock` turns into `preferNewSplit`. - runCreateAction( - isFeatureEnabled(enableChatV3Agents) ? 'agent' : 'chat', - { - shouldInsert: true, - source: 'sidebar', - } - ) + runCreateAction('agent', { + shouldInsert: true, + source: 'sidebar', + }) } > diff --git a/apps/web/src/components/app/split-layout/componentRegistry.tsx b/apps/web/src/components/app/split-layout/componentRegistry.tsx index 2e306819861..af4ae0f91e9 100644 --- a/apps/web/src/components/app/split-layout/componentRegistry.tsx +++ b/apps/web/src/components/app/split-layout/componentRegistry.tsx @@ -13,6 +13,7 @@ import { getViewPreset } from '@app/features/next-soup/sidebar/soup-filter-prese import { NonMemberChannelPreview } from '@app/features/next-soup/soup-view/non-member-channel-preview'; import { SoupView } from '@app/features/next-soup/soup-view/soup-view'; import { useRecentViewFlag } from '@app/features/next-soup/use-recent-view-flag'; +import { ReminderEditorSplit } from '@app/features/reminders/ReminderEditorSplit'; import { SettingsPanelComponentWrapper } from '@app/features/settings/Settings'; import { useAnalytics } from '@app/lib/analytics/analytics-context'; import { useFeatureFlag, usePosthog } from '@app/lib/analytics/posthog'; @@ -190,7 +191,12 @@ export function resolveComponent( } } const fallback = REGISTRY.get('inbox'); - if (fallback) return fallback; + if (fallback) { + return { + element: () => fallback.factory(params ?? {}), + initialMeta: fallback.initialMeta, + }; + } throw new Error(`Component '${name}' not registered`); } return { diff --git a/apps/web/src/components/app/split-layout/components/SplitHeader.tsx b/apps/web/src/components/app/split-layout/components/SplitHeader.tsx index b73dd5b0020..f96456517d4 100644 --- a/apps/web/src/components/app/split-layout/components/SplitHeader.tsx +++ b/apps/web/src/components/app/split-layout/components/SplitHeader.tsx @@ -100,7 +100,7 @@ function SplitBackButton() { square size="sm" class="p-1 rounded-lg touch:active:bg-transparent" - label={() => t('Go Back')} + label={t('Go Back')} hotkey={TOKENS.split.go.back} disabled={!context.handle.canGoBack()} onClick={() => { @@ -121,7 +121,7 @@ function SplitForwardButton() { square size="sm" class="p-1 rounded-lg touch:active:bg-transparent" - label={() => t('Go Forward')} + label={t('Go Forward')} hotkey={TOKENS.split.go.forward} disabled={!context.handle.canGoForward()} onClick={context.handle.goForward} @@ -278,7 +278,7 @@ function SoupNavigationButtons() {
-
@@ -229,12 +229,12 @@ function EmailFormNew(props: { }); createEffect(() => { - if (sentEmailCode(submission.result)) { - props.setStage(Stage.Verify); + if (submission.result === 'LoggedIn') { + props.setStage(Stage.Done); } else if (submission.result === 'isPasswordLogin') { setIsPasswordLogin(true); - } else if (submission.result === 'LoggedIn') { - props.setStage(Stage.Done); + } else if (sentEmailCode(submission.result)) { + props.setStage(Stage.Verify); } }); @@ -267,7 +267,7 @@ function EmailFormNew(props: { Continue - @@ -281,7 +281,7 @@ const verifyCode = action(async (formData: FormData) => { const email = formData.get('email'); if (typeof email !== 'string') throw new Error('Invalid email'); - const result = await authServiceClient.passwordlessCallback({ code, email }); + const result = await passwordlessCallback({ code, email }); if (result.isErr()) { if (result.error.some((err) => err.code === 'UNAUTHORIZED')) { throw new Error('Invalid code.'); @@ -442,7 +442,7 @@ function VerifyFormNew(props: { Verify - @@ -492,9 +492,10 @@ export function Login(props: { signupMode?: boolean }) { ? rawToken[rawToken.length - 1] : rawToken; if (session_code && typeof session_code === 'string') { - unsetTokenPromise(); - authServiceClient.sessionLogin({ session_code }).then(async (res) => { + void (async () => { + const res = await sessionLogin({ session_code }); if (res.isOk()) { + unsetTokenPromise(); await invalidateAllAfterLogin(); await initEmailLink().match( () => {}, @@ -505,7 +506,7 @@ export function Login(props: { signupMode?: boolean }) { } ); } - }); + })(); } }); diff --git a/apps/web/src/features/block-automation/component/Automation.tsx b/apps/web/src/features/block-automation/component/Automation.tsx index 22232571bd4..f953299cb9f 100644 --- a/apps/web/src/features/block-automation/component/Automation.tsx +++ b/apps/web/src/features/block-automation/component/Automation.tsx @@ -12,6 +12,7 @@ import { useBlockId } from '@core/block'; import { EntityIcon } from '@core/component/EntityIcon'; import { toast } from '@core/component/Toast/Toast'; import { blockNameToDefaultFile } from '@core/constant/allBlocks'; +import { getAppCapabilities } from '@core/constant/featureFlags'; import { whenSettled } from '@core/util/whenSettled'; import { formatDateAndTime } from '@entity'; import CopyIcon from '@phosphor/copy.svg'; @@ -125,6 +126,15 @@ function HistoryList(props: { records: HistoryRecord[]; isPending: boolean }) { } export function Automation() { + const capabilities = getAppCapabilities(); + if (!capabilities.scheduledActions) { + return ( +
+ Scheduled actions are unavailable in this profile. +
+ ); + } + const scheduleId = useBlockId(); const panel = useSplitPanelOrThrow(); const { replaceOrInsertSplit } = useSplitLayout(); diff --git a/apps/web/src/features/block-email/component/MessageCard.tsx b/apps/web/src/features/block-email/component/MessageCard.tsx index ca823ebf788..9384c2dd016 100644 --- a/apps/web/src/features/block-email/component/MessageCard.tsx +++ b/apps/web/src/features/block-email/component/MessageCard.tsx @@ -61,7 +61,7 @@ export function MessageCard(props: MessageCardProps) {