diff --git a/.gitignore b/.gitignore index 5532bb67..43db20bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ -# Local tooling / agent workspace -.claude/ -.compare/ +# Repository metadata and local agent workspaces +/.git/ +/.claude/ +/.compare/ -# Project planning / internal docs -CLAUDE.md +# Project planning, internal docs, and local-only development files +/.specs/ +/docs/ +/tools/ +/tests/ +/CLAUDE.md ask-*.md -docs/ !assets/css/routes/docs/ assets/css/routes/docs/* !assets/css/routes/docs/toolbar-search-enhancements.css @@ -16,7 +20,7 @@ assets/admin/node_modules/ assets/admin/.vite-temp/ # PHP installed dependencies -vendor/ +/vendor/ # Temporary / recovery helpers restore-helper-monoliths.php @@ -25,7 +29,3 @@ restore-helper-monoliths.php .DS_Store Thumbs.db *.log - - -# Generated contract audit artifact -tools/admin-contract-report.json diff --git a/.specs/baselines/widget-reference.json b/.specs/baselines/widget-reference.json deleted file mode 100644 index 03dc773d..00000000 --- a/.specs/baselines/widget-reference.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "schema": "aksrt.widget-reference.v1", - "captured_at": "2026-08-17", - "source_root": "C:\\Users\\Xing\\Desktop\\zibll", - "source_policy": { - "modern_widget_count": 37, - "include_core_product_marketing": true, - "exclude_bbs_branch": true, - "exclude_shop_branch": true, - "exclude_deprecated_widgets": true, - "branding": "AKSRT" - }, - "files": [ - {"path":"inc/widgets/widget-class.php","bytes":2123,"sha256":"92e8ae46c47cc6d9f46312113e182105b013c8073c29da61ef048680c9cc814b"}, - {"path":"inc/widgets/widget-import.php","bytes":146458,"sha256":"dca6b442e35f1804a7fe9130e7df9ac4c11772c85774c1faa69ebacb48df9b62"}, - {"path":"inc/widgets/widget-index.php","bytes":18826,"sha256":"6f15b1b73f909ea7eccf4697b5274a38512deae2e931ae7b0a33e48a5fcd8b25"}, - {"path":"inc/widgets/widget-more.php","bytes":167708,"sha256":"07ea0c3260a9ee9701b1ce03795941fbbfb61c377ad71fe8f7820107763c5528"}, - {"path":"inc/widgets/widget-posts.php","bytes":99676,"sha256":"20b04007270ecfd265adaaa287eb1a696566385b1f86379079be9664cfaea409"}, - {"path":"inc/widgets/widget-produck.php","bytes":121409,"sha256":"77194592685b50e5428c6db4d0e2ca2cbc52d0ff4a81200f4e10b56e26159b9d"}, - {"path":"inc/widgets/widget-slider.php","bytes":45007,"sha256":"a06a12500cb8ffbf57e496d9979eaed0edd545499ecb9c80d011d3ae49d2244e"}, - {"path":"inc/widgets/widget-user.php","bytes":24655,"sha256":"97a4fde11d0a93af229e499f9b11c8d769b21b6b7500eac9bb94b4dd850f6b24"}, - {"path":"inc/csf-framework/classes/widget-options.class.php","bytes":40070,"sha256":"ac7af19cc5d981eff933417430f45eb5bb0b1ff7dc8a0462238b79c9bab74722"}, - {"path":"css/widget-set.css","bytes":5567,"sha256":"a11605b3e30d2ee372438c4242929461cbcc369fed1fa417a8046b4f15ae8a9f"}, - {"path":"js/widget-set.js","bytes":35437,"sha256":"bdb9ac07039ec85f1ac90a92ca4496f0c625b7bdeea6a468bd9a30814e968d8f"} - ], - "groups": { - "general": ["search","posts_toc","quote","icon_cards","video","embed","media_cards","icon_cover_cards","partners","combo_info","text_title","buttons","layout_gap","tag_cloud","recent_comments","links","notice"], - "content": ["term_cards","term_aggregation","hot_posts","post_pay","posts","tab_posts","oneline_posts"], - "knowledge_product": ["pricing_plans","reviews","faq","stats","highlights","feature_intro","tilt_cases"], - "slider": ["slider","slider_media_combo"], - "user": ["user_card","author_card","user_ranking","user_list"] - }, - "excluded_legacy": ["widget_ui_main_tab_posts","widget_ui_mini_tab_posts","widget_ui_mini_posts","widget_ui_mian_posts","widget_ui_slider"], - "excluded_paths": ["inc/functions/bbs/widgets","inc/functions/shop/widgets"] -} diff --git a/.specs/plans/aksrt-widgets-rewrite.md b/.specs/plans/aksrt-widgets-rewrite.md deleted file mode 100644 index 77cc6a70..00000000 --- a/.specs/plans/aksrt-widgets-rewrite.md +++ /dev/null @@ -1,40 +0,0 @@ -# Plan: AKSRT 小工具系统全量重写 - -> 用户确认基线:`C:\Users\Xing\Desktop\zibll` -> 目标:37 个现代核心小工具;排除论坛、商城和废弃旧版;无兼容、迁移或双路运行。 - -## Architectural Decisions - -- 使用全新的 `AKSRT\WidgetSystem` 数据驱动内核,不引入 Codestar/CSF。 -- 新实例 ID 统一为 `aksrt_widget_`,不注册或读取现有小工具 ID。 -- 开发期新系统在未接入 bootstrap 前保持不可达;最终以单次注册器切换删除旧运行路径。 -- 参考“专题”映射为 `category`/`post_tag`;付费类型只使用 AKSRT `DomainModel`。 -- 最终树只保留一套小工具 CSS、JS、REST 与挂载区域实现。 - -## Phase 1 — 参考基线与原生内核 - -- [x] 冻结参考文件哈希、37 项现代组件清单和明确排除项。 -- [x] 实现 `WidgetDefinition`、`WidgetRegistry`、字段 schema、通用字段与递归规范化。 -- [x] 实现后台表单渲染、懒加载控制器及独立 admin CSS/JS 基础。 -- [x] 增加注册表/字段契约审计并通过 PHP 语法与审计检查。 - -## Phase 2 — 37 个渲染器与动态数据接口 - -- [x] 实现 17 个通用/装饰组件。 -- [x] 实现 7 个文章/知识付费组件和统一查询、卡片、分页链路。 -- [x] 实现 7 个知识产品、2 个幻灯片和 4 个用户组件。 -- [x] 实现统一 REST items 路由、一言路由及按需前端资源。 - -## Phase 3 — 原子切换、挂载区域与导入导出 - -- [x] 替换注册器并删除旧小工具类、旧公共层、旧路由和重复资源。 -- [x] 重写 40 个静态挂载区域、动态页面区域及所有模板消费点。 -- [x] 实现 `aksrt.widgets.v1` 导入导出和 5 套知识付费模板。 -- [x] 核对默认 WordPress 小工具注销策略与区块编辑器/自定义器行为。 - -## Phase 4 — 自动化与视觉终验 - -- [x] 完成 schema、查询、可见性、导入导出、REST 与支付适配测试。 -- [ ] 生成参考源码黄金 fixture,并完成 1440/1024/390、明暗模式截图矩阵。 -- [ ] 验证交互、无障碍、控制台、网络和像素差异阈值。 -- [x] 复跑 WordPress 项目检测、全量 PHP 检查和管理端契约审计。 diff --git a/.specs/plans/frontend-modal-identity-badges.md b/.specs/plans/frontend-modal-identity-badges.md deleted file mode 100644 index bb503a93..00000000 --- a/.specs/plans/frontend-modal-identity-badges.md +++ /dev/null @@ -1,80 +0,0 @@ -# Plan: Frontend Modal Identity and Badges - -## Architectural Decisions - -- **Dependency**: This plan starts only after `frontend-modal-runtime` Phase 2 supplies `AksrtModalRuntime`, loader/cache/state contracts, telemetry marks, and the rollout reader. -- **Authentication snapshot**: `AuthApply` and `ApplicationRepository` expose one current-user snapshot containing verification state, latest application, form payload, allowed next action, and poll metadata. The REST handler and owner permission callback reuse that loaded application instead of querying by UUID twice. -- **External provider boundary**: Aliyun/Tencent validation and refresh stay behind `ApplicationService`. Provider HTTP work starts after the shell is visible, is cancellable/visibility-aware where possible, and cannot prevent the modal from opening. -- **Badge canonical path**: The JSON REST path is the only new badge read/write path. User-center and public author entry points share the same privacy-aware DTO; old HTML AJAX modal registrations and post-wear remote reopening are removed after cutover. -- **Privacy**: Cache keys include user/object/context and public-display mode. Authentication evidence remains owner/capability protected and no-store; public badge data follows `list_for_user()` privacy rules. No private DTO is persisted in browser storage. -- **Domain cache**: Badge owned/wear maps and authentication snapshots use short in-memory/request-context memoization. Any submit, review, wear, or status transition invalidates the affected tag before rendering the next view. -- **Admin boundary**: Existing native authentication review and badge management remain the operational surface. This plan does not move them into the Admin SPA. - ---- - -## Phase 1 — Authentication status appears quickly and refreshes safely - -**User stories**: US8, US9, US13. - -### What to build - -Replace the three independent SSR facade reads in `inc/helpers/user-center-renderers/profile-auth.php` with one typed snapshot from `src/User/AuthApply.php`/`src/User/ApplicationRepository.php`. Shape the current identity REST response around the same snapshot and reuse the application loaded by `owns_application()` in `src/Identity/RestController.php` inside get/refresh/manual handlers. Update `assets/js/routes/user-center-auth.js` to open the unified shell immediately, render snapshot state, defer provider refresh until the modal is open or the user explicitly refreshes, pause while hidden, apply backoff, and cancel stale refreshes. Preserve the existing create, manual evidence, provider callback, nonce, owner, capability, and sensitive no-store boundaries. - -### Done when - -- [ ] `profile-auth.php`, `AuthApply`, and `ApplicationRepository` produce one active/latest/verified snapshot per request; browser harness scenario `auth-snapshot-query-count` with `SAVEQUERIES` shows no repeated identical application SELECT for the active panel. -- [ ] `RestController` owner permission and get/refresh/manual handlers reuse one loaded application per request; `php -l src/User/AuthApply.php src/User/ApplicationRepository.php src/Identity/RestController.php src/Identity/ApplicationService.php` passes. -- [ ] Browser scenario `auth-modal-deferred-refresh` verifies shell-first rendering, no polling while the modal is closed, pause on hidden visibility, minimum interval/backoff, cancellation on close, and a useful error when the provider exceeds its timeout. -- [ ] Browser/API scenarios cover create, provider-pending, processing, verified, rejected/manual retry, evidence upload, duplicate submit, unauthorized owner, and sensitive evidence isolation; existing native review changes are reflected on the next snapshot. - ---- - -## Phase 2 — Badge modal uses one cached JSON path - -**User stories**: US10, US11, US12, US13. - -### What to build - -Refactor `src/User/Badges.php` so `list_for_user()`, `get_owned_map()`, and `get_wear()` share one context and do not rebuild owned/wear dictionaries repeatedly. Remove duplicate `Badges::init()` calls from `functions.php`. Update `assets/js/user-badges.js` to use the runtime loader, short cache, skeleton/error/retry states, and tag invalidation after wear. Replace the user-center and public author HTML/AJAX modal entry points in `inc/helpers/user-center-renderers/profile-header.php`, `templates/parts/user/author-hero.php`, and `assets/js/core/remote-modal.js` with the canonical privacy-aware JSON path. The old `wp_ajax(_nopriv)_aksrt_*badge*modal` handlers and post-wear remote reopen must disappear after this cutover. - -### Done when - -- [ ] `SAVEQUERIES` scenario `badge-read-query-count` shows one shared owned/wear/dictionary read context per badge response, and a repeated open for the same user/context uses the runtime cache without cross-user reuse. -- [ ] `assets/js/user-badges.js` displays skeleton/empty/error/retry states, re-renders the current list after wear, and no longer calls `AksrtRemoteModal.openRemote` after a successful wear; `node --check assets/js/user-badges.js assets/js/core/remote-modal.js` passes. -- [ ] `git grep -n "aksrt_user_badge_info_modal\|aksrt_single_badge_info_modal\|get_info_link"` shows no active target entry after the canonical path is enabled, and `functions.php` contains one badge singleton initialization. -- [ ] Browser/API scenarios cover no badges, earned/unearned/in-progress/detail, wear success/failure, public author privacy, self/manager visibility, unauthenticated access, REST nonce, and no leakage through cache keys. - ---- - -## Phase 3 — Authentication and badges are independently releasable - -**User stories**: US1, US2, US3, US11, US12, US13, US14. - -### What to build - -Connect identity and badge loaders to the per-domain rollout values from `frontend-modal-runtime`. Make successful authentication transitions, manual review results, badge wear, and badge management invalidate the appropriate runtime tags and update header/profile signals without a full reload. Exercise the two domains as separate gray releases, then remove the old domain-specific modal branches once each switch is permanently enabled. - -### Done when - -- [ ] The authentication and badge rollout values independently select the new loader path; toggling one does not change the other and does not create a second data store or dual-read branch. -- [ ] `php tools/audit-admin-contracts.php` exits 0 and `node --check assets/js/routes/user-center-auth.js assets/js/user-badges.js` passes after the final route/schema changes. -- [ ] Desktop/mobile scenarios verify shell/ready/error budgets, state refresh behind the modal, focus/keyboard behavior, no horizontal overflow, and no stale private data after logout or account switch. -- [ ] The gray-release checklist records shell/ready/error/status-mismatch metrics and demonstrates pausing the next rollout stage when identity or badge errors exceed the agreed threshold. - ---- - -## Out of Scope - -- 修改会员价格、会员权益、支付网关规则、订单账务、退款核算、认证资格/审核政策或徽章授予条件;这些规则只允许被新的流程正确调用和展示。 -- 新增支付渠道、替换支付供应商、在浏览器保存支付凭证,或改变支付回调/异步结算机制。 -- 新增公开认证资料库、向其他用户暴露认证文档、跨用户查看订单或徽章数据。 -- 把订单、售后、认证审核或徽章运营工作台迁移进配置型 SPA;日常运营仍属于原生 WordPress 后台。 -- 全站页面导航、用户中心信息架构、品牌设计系统或所有非弹窗页面的全面重做;本期只统一弹窗运行时和四类流程所需的轻量体验。 -- 强制页面加载时预取所有弹窗数据、长期持久化敏感弹窗数据、建立订单/认证/徽章的第二套事实存储或长期保留旧新双路实现。 -- 以新增兼容别名、旧 option 双读、静默降级或数据迁移层解决旧实现问题;流程切换后以新的规范化链路为唯一实现。 -- 建立独立的数据仓库、复杂转化漏斗或第三方用户行为追踪系统;本期观测只服务于弹窗性能、错误和业务状态闭环。 - -## Open Questions - -- 认证云供应商是否支持在请求取消/页面隐藏时安全停止远端刷新;若不支持,客户端只能停止轮询并让服务端回调/下次打开完成状态确认。 -- 徽章公开作者入口最终是否必须支持未登录访问;默认保留现有公开可见 DTO,但统一路径仍必须执行业务层 privacy 过滤。 diff --git a/.specs/plans/frontend-modal-loading-upgrade.md b/.specs/plans/frontend-modal-loading-upgrade.md deleted file mode 100644 index 4438355c..00000000 --- a/.specs/plans/frontend-modal-loading-upgrade.md +++ /dev/null @@ -1,84 +0,0 @@ -# Plan: Frontend Modal Loading Upgrade - -This is the coordination plan for the three executable plans below. Implementation work should run in dependency order; the detailed phase checklists live in the linked plan files. - -## Architectural Decisions - -- **Execution split**: The shared modal runtime is a prerequisite. Identity/badges and orders/VIP can then proceed in parallel, each with its own domain API, UI, query optimization, and gray-release evidence. -- **One canonical runtime**: `AksrtModalRuntime` owns shell, skeleton, lifecycle, cancellation, cache, state, focus, and telemetry. Domain scripts only provide loaders and view models. -- **Canonical data boundaries**: Keep `aksrt/v1`, existing domain services, native operational admin, and `AKSRT_Settings` as the boundaries. No duplicate facts, compatibility reads, or long-lived old/new modal implementations. -- **No core table replacement**: Use existing order, identity, user-meta, payment, and badge data. The orders plan may add the required composite index through the existing DB migrator and schema-version process. -- **Staged release**: Roll out identity/badges first, then orders, then VIP payment. Each domain has an independent temporary switch and must meet the performance/error budget before the next switch is enabled. - ---- - -## Phase 1 — Shared modal runtime is ready for domain work - -**Detailed plan**: `.specs/plans/frontend-modal-runtime.md` - -**User stories**: US1–3, US12–14. - -### What to build - -Complete both runtime phases: baseline/rollout observability and the cancellable shell/loader/cache/state contract. This phase is a foundation exception because all four existing flows currently own incompatible modal shells and request lifecycles; the runtime is demoable through the existing remote-modal entry path before domain JSON migrations begin. - -### Done when - -- [ ] `.specs/plans/frontend-modal-runtime.md` Phase 1 and Phase 2 checklists are checked, `php tools/audit-admin-contracts.php` exits 0, and `node --check` passes for all runtime JS. -- [ ] Browser scenarios `modal-baseline`, `modal-shell-lifecycle`, and `modal-cache-dedupe` show shell-first behavior, cancellation, deduplication, invalidation, focus restoration, and cold/warm metrics. -- [ ] `git grep` inventory identifies every old remote-modal caller and labels the remaining domain migration owner before either business plan starts. - ---- - -## Phase 2 — Identity/badges and orders/VIP have vertical slices - -**Detailed plans**: `.specs/plans/frontend-modal-identity-badges.md` and `.specs/plans/frontend-modal-orders-vip.md` - -**User stories**: US4–11, US13. - -### What to build - -Run the identity/badge plan and orders/VIP plan in parallel after Phase 1. Each plan must finish its read path, write path, query/API/UI integration, permission handling, and browser regression scenarios before its gray-release phase. - -### Done when - -- [ ] `.specs/plans/frontend-modal-identity-badges.md` Phases 1–2 and `.specs/plans/frontend-modal-orders-vip.md` Phases 1–2 are checked with their named `SAVEQUERIES`, PHP, JS, API, and browser scenarios passing. -- [ ] Identity/badge and order/VIP loaders use independent cache/invalidation tags and cannot reuse another user or domain's payload. -- [ ] The native order/after-sale and identity/badge operational surfaces remain usable; no operational workbench is added to the Admin SPA. - ---- - -## Phase 3 — All domains are gray-released and old paths are removed - -**Detailed plans**: the Phase 3 sections of all three detailed plans. - -**User stories**: US1–14. - -### What to build - -Release identity/badges, then orders, then VIP payment using independent switches. Compare shell/ready/error/status-mismatch metrics against the PRD budgets, pause the next domain on regression, update user-center summaries after successful writes, and remove old HTML/AJAX/domain-specific modal calls after each domain is permanently enabled. - -### Done when - -- [ ] The Phase 3 checklist in each detailed plan is checked, with `php tools/audit-admin-contracts.php`, changed-file `php -l`, and changed-file `node --check` all passing. -- [ ] Desktop and mobile evidence shows approximately 100ms shell visibility, 200ms cache-hit content, p75 ≤800ms cold content, and p95 ≤1.5s initial budget, with errors/status mismatches recorded per domain. -- [ ] `git grep` confirms no active target old HTML badge modal, duplicate VIP purchase/poll coordinator, hard-coded order REST root, or bypassing domain loader remains after cutover. -- [ ] The final release record identifies the removal version for temporary rollout settings and confirms no compatibility alias, old option double-read, second fact store, or long-lived dual implementation remains. - ---- - -## Out of Scope - -- 修改会员价格、会员权益、支付网关规则、订单账务、退款核算、认证资格/审核政策或徽章授予条件;这些规则只允许被新的流程正确调用和展示。 -- 新增支付渠道、替换支付供应商、在浏览器保存支付凭证,或改变支付回调/异步结算机制。 -- 新增公开认证资料库、向其他用户暴露认证文档、跨用户查看订单或徽章数据。 -- 把订单、售后、认证审核或徽章运营工作台迁移进配置型 SPA;日常运营仍属于原生 WordPress 后台。 -- 全站页面导航、用户中心信息架构、品牌设计系统或所有非弹窗页面的全面重做;本期只统一弹窗运行时和四类流程所需的轻量体验。 -- 强制页面加载时预取所有弹窗数据、长期持久化敏感弹窗数据、建立订单/认证/徽章的第二套事实存储或长期保留旧新双路实现。 -- 以新增兼容别名、旧 option 双读、静默降级或数据迁移层解决旧实现问题;流程切换后以新的规范化链路为唯一实现。 -- 建立独立的数据仓库、复杂转化漏斗或第三方用户行为追踪系统;本期观测只服务于弹窗性能、错误和业务状态闭环。 - -## Open Questions - -- 部署是否已有统一的浏览器 RUM/Server-Timing 收集端;默认先提供 marks、响应头和现有部署观测,不新增主题指标表。 -- 复合订单索引与临时 rollout 设置的删除版本需要在最终发布记录中确认。 diff --git a/.specs/plans/frontend-modal-orders-vip.md b/.specs/plans/frontend-modal-orders-vip.md deleted file mode 100644 index ce086a07..00000000 --- a/.specs/plans/frontend-modal-orders-vip.md +++ /dev/null @@ -1,83 +0,0 @@ -# Plan: Frontend Modal Orders and VIP - -## Architectural Decisions - -- **Dependency**: This plan starts only after `frontend-modal-runtime` Phase 2 supplies the canonical shell, loader, cache/state, cancellation, and telemetry contracts. -- **Order view model**: `UserOrders` and `OrderCenterRest` return a minimal list/detail view model with server-derived `allowed_actions`, status version, updated time, and invalidation tags. The front end never derives payment/cancel/after-sale eligibility from local status. -- **Order query shape**: Combine list/stat work where possible, batch resource decoration, and add the missing `user_id/status/created_at` index through `AKSRT_DB_Migrator` with a schema version bump. No new order table or duplicate fact store. -- **Reconciliation boundary**: `Gateway::reconcile_order()` remains the authority for pending external orders but runs after the detail shell is visible and only when needed. A normal completed/non-external detail must not wait for remote provider HTTP. -- **VIP JSON path**: Replace server-generated full HTML from `VipServiceModal` with a minimal JSON view model. `pay-vip.js` and the VIP shop in `credits.js` share one purchase coordinator; catalog, member state, order object, and payment attempt are reused within a request. -- **Payment safety**: Payment gateways and `PaymentOrchestrator` stay server-side. Client polling is bounded, backoff-aware, visibility-aware, cancellable, and terminated by a terminal order state. Core price, eligibility, ledger, callback, and VIP fulfillment rules are unchanged. -- **API configuration**: All order/VIP requests use the globally injected API root and REST nonce. Remove `order-center.js` hard-coded REST construction and do not add compatibility route aliases. -- **Release**: Order and VIP rollout switches are independent. Old paths may remain inactive while a domain is not released, but each domain has one canonical implementation after its switch is permanently enabled. - ---- - -## Phase 1 — Order details appear before remote state reconciliation - -**User stories**: US6, US7, US11, US13. - -### What to build - -Optimize `src/Payment/UserOrders.php` and `src/Payment/OrderRepositoryCore.php` so the order list does not perform a fixed list plus four COUNT queries and resource rows do not cause post/meta N+1 work. Add the composite order index in `src/Core/DBMigratorPart2.php` and bump the schema version through the existing migrator. Shape `src/Payment/OrderCenterRest.php` responses around minimal list/detail view models and preserve owner checks. Update `src/Payment/Gateway.php` so pending external reconciliation is deferred or represented as a refresh state after the shell is visible. Replace `order-center.js` hard-coded `siteUrl/wp-json` construction with injected `apiUrl/restUrl`, then move detail, pay, cancel, refresh, and invalidation through `AksrtModalRuntime`. - -### Done when - -- [ ] `SAVEQUERIES` scenario `order-list-query-count` verifies that the normal first page no longer issues list + four independent COUNT queries and that resource-order decoration has no per-row post/meta N+1; empty/overflow pages remain correct. -- [ ] `DBMigratorPart2` defines the `user_id/status/created_at` query index and the active schema version is bumped; after migration, `SHOW INDEX` or the WordPress DB harness verifies the index. -- [ ] Browser scenario `order-detail-shell` verifies summary skeleton first, ordinary detail does not wait for provider HTTP, pending reconciliation reports refresh state, and detail/status/list update after pay/cancel/after-sale responses. -- [ ] `order-center.js` reads the injected API root, and `php -l src/Payment/UserOrders.php src/Payment/OrderRepositoryCore.php src/Payment/OrderCenterRest.php src/Payment/Gateway.php src/Core/DBMigratorPart2.php` plus `node --check assets/js/order-center.js` pass. -- [ ] API/browser cases cover another-tab status changes, unauthorized order numbers, duplicate action clicks, timeout/retry, pagination, and no cross-user cache reuse. - ---- - -## Phase 2 — VIP purchase reaches payment result through one coordinator - -**User stories**: US4, US5, US11, US13. - -### What to build - -Change `src/Content/VipServiceRest.php` and `src/Content/VipServiceModal.php` from full HTML modal responses to a minimal JSON view model that contains the server-calculated catalog, member state, payment methods, allowed actions, and version metadata. Refactor `src/Content/VipServicePurchase.php` and `src/Payment/PaymentOrchestrator.php` to reuse the catalog/member/order context instead of reading the same data after create and again during pay. Consolidate the remote modal path in `assets/js/pay-vip.js` and the embedded shop path in `assets/js/credits.js` into one purchase/poll coordinator; keep the existing `credits-vip.php` and `vip-tab.php` entry points but route their actions through it. Replace the fixed 40 × 3-second loop with bounded backoff, visibility handling, abort, and terminal-state invalidation. - -### Done when - -- [ ] `SAVEQUERIES` scenario `vip-purchase-query-count` shows no duplicate member-level/catalog/order reads for modal load and purchase create/pay; `VipServiceRest` reuses the catalog user level instead of recomputing it. -- [ ] `pay-vip.js` and `credits.js` share one VIP purchase/poll coordinator; `git grep -n "setInterval\|pay/content/order\|pay-vip/modal" assets/js/pay-vip.js assets/js/credits.js` shows no second fixed 40 × 3-second loop or old full-HTML injection path. -- [ ] Browser scenario `vip-purchase-flow` covers not-enrolled, active, expired, renewal/upgrade, package/price, points exchange, gateway redirect, cancel, failure, async success, duplicate click, and return to order detail. -- [ ] Payment response inspection confirms no gateway secret reaches the browser; existing gateway, order callback, VIP fulfillment, core price, and entitlement rules pass regression scenarios. -- [ ] `php -l src/Content/VipServiceRest.php src/Content/VipServiceModal.php src/Content/VipServicePurchase.php src/Payment/PaymentOrchestrator.php` and `node --check assets/js/pay-vip.js assets/js/credits.js` pass. - ---- - -## Phase 3 — Orders and VIP are independently gray-releasable - -**User stories**: US1–US7, US11–US14. - -### What to build - -Connect order and VIP coordinators to the independent rollout switches, invalidate user-center order/VIP summaries after successful writes, and verify native order/after-sale operations remain the operational backend. Release orders first, measure shell/ready/error/status mismatch, then release VIP payment only after the order slice is stable. Remove each domain's old modal calls once its switch is permanently enabled. - -### Done when - -- [ ] Order and VIP rollout values independently select the new loaders; toggling one does not change the other or create a second order/member fact store. -- [ ] `php tools/audit-admin-contracts.php` exits 0, all changed PHP files pass `php -l`, and `node --check assets/js/order-center.js assets/js/pay-vip.js assets/js/credits.js` passes. -- [ ] Desktop/mobile scenarios verify shell/ready budgets, cache-hit and cold-load p75/p95, payment/order state synchronization, permission errors, retry behavior, focus, and no horizontal overflow. -- [ ] Gray-release evidence shows orders reaching the agreed 100ms shell, 200ms cache-hit, p75 ≤800ms, and p95 ≤1.5s initial budgets before VIP is enabled; rollback is demonstrated by disabling only the affected domain switch. - ---- - -## Out of Scope - -- 修改会员价格、会员权益、支付网关规则、订单账务、退款核算、认证资格/审核政策或徽章授予条件;这些规则只允许被新的流程正确调用和展示。 -- 新增支付渠道、替换支付供应商、在浏览器保存支付凭证,或改变支付回调/异步结算机制。 -- 新增公开认证资料库、向其他用户暴露认证文档、跨用户查看订单或徽章数据。 -- 把订单、售后、认证审核或徽章运营工作台迁移进配置型 SPA;日常运营仍属于原生 WordPress 后台。 -- 全站页面导航、用户中心信息架构、品牌设计系统或所有非弹窗页面的全面重做;本期只统一弹窗运行时和四类流程所需的轻量体验。 -- 强制页面加载时预取所有弹窗数据、长期持久化敏感弹窗数据、建立订单/认证/徽章的第二套事实存储或长期保留旧新双路实现。 -- 以新增兼容别名、旧 option 双读、静默降级或数据迁移层解决旧实现问题;流程切换后以新的规范化链路为唯一实现。 -- 建立独立的数据仓库、复杂转化漏斗或第三方用户行为追踪系统;本期观测只服务于弹窗性能、错误和业务状态闭环。 - -## Open Questions - -- 订单/支付环境是否提供现有 Server-Timing 或网关状态监控收集端;默认使用响应头和现有部署观测,不新增主题内指标表。 -- 复合订单索引在生产大表上的部署窗口需要由发布流程确认;迁移必须遵循现有 `AKSRT_DB_Migrator` 原子升级约束。 diff --git a/.specs/plans/frontend-modal-runtime.md b/.specs/plans/frontend-modal-runtime.md deleted file mode 100644 index 518b689d..00000000 --- a/.specs/plans/frontend-modal-runtime.md +++ /dev/null @@ -1,72 +0,0 @@ -# Plan: Frontend Modal Runtime - -## Architectural Decisions - -- **Canonical runtime**: `assets/js/core/modal-runtime.js` is the single `AksrtModalRuntime` seam for shell creation, lifecycle, focus, loading, error, cancellation, and refresh. `assets/js/core/modal-triggers.js` only binds the new declarative trigger contract; no legacy modal alias or compatibility bridge exists. -- **Loader contract**: A loader receives a domain context and abort signal and returns a minimal view model plus render/update callbacks. `AksrtModalRuntime.openRemote({responseType: 'json', renderData})` owns the request, cache, cancellation, and lifecycle while the domain renderer owns only view-model markup. It may read only the active modal's data. -- **Cache contract**: Use in-memory, user-scoped cache entries with short TTLs, in-flight request deduplication, explicit invalidation tags, and complete clearing on logout/account switch. Do not persist private modal payloads in localStorage. -- **State contract**: All domains use the same `idle`, `loading`, `ready`, `empty`, `submitting`, `success`, `error`, `unauthorized`, and `stale` states. Skeletons match the final layout and never represent business truth. -- **API boundary**: Existing `aksrt/v1` domain routes remain the service boundary; HTML modal responses are replaced domain by domain with minimal JSON view models. Every changed REST route keeps a `permission_callback`; AJAX is not a new data path. -- **Request adapter**: Add one thin `AksrtRequest` adapter for the injected REST root, nonce headers, JSON/error envelopes, 401 handling, and abort signals. Domain files stop maintaining their own REST-root and response-parsing variants. -- **Rehydrate and feedback**: Every runtime-rendered HTML response calls `AksrtFrontend.rehydrate(body, 'modal')`; shared feedback calls the existing callable `AksrtToast` API directly. There is one binding path and no compatibility fallback. -- **Rollout**: Add a temporary per-domain rollout section under the existing Tools configuration surface, read through `AKSRT_Settings`. One canonical new implementation is enabled per domain; the switch is for staged release, not a compatibility data layer. -- **Telemetry**: Use browser `performance.mark/measure` and server `Server-Timing`/existing deployment observability by default. Do not add a private payload or payment/identity-content event store in this foundation plan. -- **Build boundary**: Frontend assets are plain theme JavaScript, not the Admin SPA bundle. Run `node --check` for changed JS and run the admin contract audit only when the rollout schema/controller changes. - ---- - -## Phase 1 — Modal latency is measurable and rollout is controllable - -**User stories**: US1, US2, US3, US14. - -### What to build - -Create the typed rollout reader and configuration contract, then add a small telemetry module that marks the existing VIP, order, identity, and badge open flows without changing their business behavior. Establish cold-cache and warm-cache baselines for click-to-shell, click-to-first-content, click-to-ready, request count, SQL count, response size, and error outcome. The rollout reader must be consumed through `AKSRT_Settings` and the existing Tools schema/controller path, not a raw option read. - -Primary integration points: - -- `AKSRT_Settings` and the Tools schema/controller for `frontend_modal_rollout`. -- `assets/js/core/modal-telemetry.js` (new) and the existing feature enqueue/config injection in `src/Core/ScriptsPart1c.php` and the surface script enqueue modules. -- Existing flows in `assets/js/pay-vip.js`, `assets/js/order-center.js`, `assets/js/routes/user-center-auth.js`, and `assets/js/user-badges.js` for baseline marks only. - -### Done when - -- [x] The Tools schema, REST response, and `AKSRT_Settings` reader expose independent VIP, order, identity, and badge rollout values; `php tools/audit-admin-contracts.php` exits 0. -- [x] `assets/js/core/modal-telemetry.js` emits click, shell, first-content, ready, action-start, action-end, and error marks without including payment credentials, order contents, or identity evidence. -- [ ] Browser scenario `modal-baseline` records cold and warm measurements for all four current entry points, and the baseline artifact names the request/SQL counts used for later comparison. -- [x] `node --check assets/js/core/modal-telemetry.js assets/js/pay-vip.js assets/js/order-center.js assets/js/routes/user-center-auth.js assets/js/user-badges.js` passes. - ---- - -## Phase 2 — Any modal opens with a cancellable shell and reusable cache - -**User stories**: US1, US2, US3, US12, US13. - -### What to build - -Build the canonical `AksrtModalRuntime` in `assets/js/core/modal-runtime.js` with focused request, cache, state, and trigger modules. The runtime must show a skeleton before data, use the single new modal trigger contract, abort requests on close/replacement, deduplicate reads, invalidate explicit tags, restore focus, support keyboard/overlay close, mobile bottom-sheet behavior, retryable errors, and call `AksrtFrontend.rehydrate` after every HTML render. All domain callers use the canonical request/runtime/Toast APIs directly; no legacy alias, second host, or migration compatibility path is retained. - -### Done when - -- [x] `assets/js/core/modal-runtime.js` exposes one canonical `AksrtModalRuntime.open/close/refresh` lifecycle, `AksrtRequest` owns REST root/nonce/envelope handling, and no domain script creates a second global modal host. -- [x] `node --check assets/js/core/modal-triggers.js assets/js/core/request.js assets/js/core/modal-runtime.js assets/js/core/modal-cache.js assets/js/core/modal-state.js assets/js/pay-vip.js` passes. -- [ ] Browser scenario `modal-shell-lifecycle` verifies shell visibility within the 100ms initial budget, skeleton/error/retry states, ESC and overlay close, focus return, request cancellation, mobile bottom-sheet behavior, callable Toast, and `AksrtFrontend.rehydrate(body, 'modal')` after runtime HTML. -- [ ] Browser scenario `modal-cache-dedupe` verifies one read for concurrent/repeated opens, invalidation after a successful write, no reuse after logout/account switch, and one pay-vip binding; `git grep -n "AksrtRemoteModal\|RefreshModal\|data-aksrt-remote-modal" -- '*.js' '*.php' '*.css'` returns no matches. - ---- - -## Out of Scope - -- 修改会员价格、会员权益、支付网关规则、订单账务、退款核算、认证资格/审核政策或徽章授予条件;这些规则只允许被新的流程正确调用和展示。 -- 新增支付渠道、替换支付供应商、在浏览器保存支付凭证,或改变支付回调/异步结算机制。 -- 新增公开认证资料库、向其他用户暴露认证文档、跨用户查看订单或徽章数据。 -- 把订单、售后、认证审核或徽章运营工作台迁移进配置型 SPA;日常运营仍属于原生 WordPress 后台。 -- 全站页面导航、用户中心信息架构、品牌设计系统或所有非弹窗页面的全面重做;本期只统一弹窗运行时和四类流程所需的轻量体验。 -- 强制页面加载时预取所有弹窗数据、长期持久化敏感弹窗数据、建立订单/认证/徽章的第二套事实存储或长期保留旧新双路实现。 -- 以新增兼容别名、旧 option 双读、静默降级或数据迁移层解决旧实现问题;流程切换后以新的规范化链路为唯一实现。 -- 建立独立的数据仓库、复杂转化漏斗或第三方用户行为追踪系统;本期观测只服务于弹窗性能、错误和业务状态闭环。 - -## Open Questions - -- 部署环境是否已有 RUM/Server-Timing 收集端?默认先交付浏览器 marks 与响应头,不在主题内新增持久化指标表。 -- `frontend_modal_rollout` 的临时开关在所有领域切换完成后的删除版本,需要在发布记录中确定。 diff --git a/.specs/plans/user-center-sidebar-vip-entry.md b/.specs/plans/user-center-sidebar-vip-entry.md deleted file mode 100644 index 33674d99..00000000 --- a/.specs/plans/user-center-sidebar-vip-entry.md +++ /dev/null @@ -1,61 +0,0 @@ -# Plan: 用户中心侧栏 VIP 入口卡片 - -## Architectural Decisions - -Durable decisions that apply across all phases: - -- **现有会员导航契约**:完整点击卡片复用现有用户中心 `tab=vip` URL;不打开购买弹窗,也不新增客户端标签切换逻辑。 -- **现有配置决定可见性**:仅当现有 VIP 配置中至少一个等级启用时渲染入口;不新增设置项、管理员例外或不可用占位。 -- **现有数据决定展示状态**:复用既有 VIP 等级、过期等级和到期状态读取能力及文案语义;不增加数据字段、迁移或新的会员状态计算。 -- **侧栏组合层决定顺序**:侧栏组合器将独立 VIP 入口渲染器放在统计信息与账户资产之间;渲染器封装卡片状态和输出。 -- **用户中心样式保持局部**:会员横卡样式限定在用户中心侧栏范围,沿用现有响应式侧栏抽屉,不新增移动端内容区入口。 -- **单一会员入口位置**:删除资产页中仅作导航用途的 VIP 入口,保留独立“我的会员”页面及其购买、续费、升级、兑换和支付能力。 - ---- - -## Phase 1 — 会员入口可从侧栏进入 - -**User stories**: 1、2、3、4、7 - -### What to build - -在统计信息和账户资产之间接入独立的服务端 VIP 入口渲染器。入口根据现有配置和当前用户会员状态,展示未开通引导、有效会员等级与既有到期状态,或过期状态;整个卡片通过现有用户中心 URL 进入“我的会员”页面。此阶段先交付完整行为与基础结构,视觉增强和资产页去重留到下一阶段。 - -### Done when - -- [x] `inc/helpers/user-center-renderers/profile-sidebar.php` 在 `sidebar-stats.php` 后、`sidebar-commerce.php` 前加载独立的侧栏 VIP 入口渲染器。 -- [x] 新的侧栏 VIP 入口渲染器复用现有 VIP 配置、会员等级、过期等级和到期状态,并在没有启用任何 VIP 等级时不输出 HTML。 -- [x] [agent:test-auditor] 人工检查渲染器的四种服务端输出:未开通显示权益引导;有效会员显示等级和既有状态文案;过期会员显示过期状态;VIP 未启用时无入口,并确认未引入新的精确日期格式。 -- [x] 新入口的完整可点击区域使用 `aksrt_get_user_center_tab_url('vip')` 生成目标地址,人工检查所得 URL 可进入 `tab=vip`。 -- [x] 运行 `php tools/audit-admin-contracts.php`,命令退出码为 0 且报告为 `errors=0`。 - ---- - -## Phase 2 — 会员横卡完成视觉落地并消除重复入口 - -**User stories**: 5、6 - -### What to build - -为侧栏 VIP 入口增加与账户资产双卡明显区分的金色或会员感渐变视觉、会员图标、动态状态文本、右侧箭头及交互反馈,并确保其随既有侧栏在移动端抽屉中正确展示。同时删除资产页内容区中重复的 VIP 导航卡,保持余额、积分、签到和记录功能不变。 - -### Done when - -- [x] `assets/css/routes/user-center/shell.css` 包含限定在用户中心侧栏的 VIP 卡样式,覆盖会员渐变、图标、状态文本、右侧箭头、完整卡片悬停/聚焦状态和窄屏文本约束。 -- [x] `inc/helpers/user-center-renderers/profile-credits.php` 不再输出 `uc-vip-assets-entry` 导航卡,文件中原有余额、积分、签到和资产记录组合逻辑仍被保留。 -- [ ] [agent:test-auditor] 人工在桌面宽度验收“统计信息 → VIP 入口 → 账户资产”的顺序、整卡跳转和 VIP 卡与余额/积分双卡的视觉区分。 -- [ ] [agent:test-auditor] 人工在既有移动侧栏抽屉验收相同顺序、整卡可点击性、状态文本无溢出,并确认页面内容区没有额外复制 VIP 入口。 -- [x] 运行 `php tools/audit-admin-contracts.php`,命令退出码为 0 且报告为 `errors=0`。 - -## Out of Scope - -- 修改“我的会员”页面的布局、文案、套餐卡、权益内容或购买流程。 -- 修改支付弹窗、支付渠道、续费、升级、兑换、卡密兑换或订单逻辑。 -- 新增、修改或迁移会员数据、会员状态算法、到期日期格式、数据库表或 REST API。 -- 新增后台会员配置、侧栏开关、管理员例外显示规则或运营统计。 -- 记录 VIP 入口的曝光、点击或转化数据。 -- 在移动端内容区额外置顶或复制 VIP 入口。 - -## Open Questions - -无。 diff --git a/.specs/plans/user-creator-center-zibll.md b/.specs/plans/user-creator-center-zibll.md deleted file mode 100644 index 8537c1a2..00000000 --- a/.specs/plans/user-creator-center-zibll.md +++ /dev/null @@ -1,104 +0,0 @@ -# Plan: 用户中心创作中心与文章价格表 - -## Architectural Decisions - -Durable decisions that apply across all phases: - -- **唯一入口使用 `posts`**:将现有“我的文章”面板升级为“创作中心”,不新增重复的 `creator` 面板或路由。 -- **复用用户中心壳**:继续使用 `AKSRT\\User\\Center`、现有 `?tab=posts`、REST panel、运行时配置、侧栏和移动端导航。 -- **复用现有投稿页**:新建和编辑文章继续跳转现有 `page-submit.php`,不把 TinyMCE 编辑器复制到用户中心。 -- **价格使用唯一业务通路**:单篇和批量价格修改必须复用 `FrontSubmitPay::save_pay_meta()`,或先将其抽取为唯一共享价格服务;不复制价格校验逻辑。 -- **价格数据模型不变**:继续使用当前 `aksrt_pay` 及统一付费元数据,订单、购买和收益逻辑无需修改。 -- **服务端权限为最终边界**:每篇文章重新检查作者归属、编辑权限和 `new_post_pay` 能力;前端选择结果不可信。 -- **批量更新采用逐项结果**:服务端返回每篇文章的成功/失败结果,前端不得把部分失败伪装成整体成功。 -- **复杂付费资源继续走完整编辑器**:价格表优先处理价格和基础付费字段;付费下载资源等复杂结构不重复实现。 - ---- - -## Phase 1 — 创作中心文章总览可端到端使用 - -**用户故事**:US1、US2、US3、US8 - -### What to build - -升级现有 `posts` 面板的名称、信息层级和文章管理体验:增加创作中心标题及新建文章主操作,整合状态概览、文章列表、编辑/预览/删除操作,并复用现有文章查询、状态筛选、权限和用户中心 REST 加载机制。 - -### Done when - -- [ ] `src/User/Center.php`、用户中心渲染映射和入口文案仍只注册/渲染 `posts`,不存在第二个重复的 `creator` 文章管理面板。 -- [ ] `profile-posts.php` 展示创作中心标题、新建文章入口、发布/草稿/待审/私密/全部状态数量,并继续使用现有状态筛选参数。 -- [ ] 文章列表展示标题、封面或占位图、状态、更新时间、付费摘要和按权限过滤后的操作按钮。 -- [ ] 编辑、预览和删除按钮分别遵循现有 `can_edit_post()`、文章可访问性和 `can_delete_post()` 判断;删除继续使用现有 nonce/AJAX 语义。 -- [ ] 通过用户中心 REST panel 和 `?tab=posts&post_status=...` 深链访问时,服务端和客户端均能激活正确面板与筛选状态。 - ---- - -## Phase 2 — 新建与编辑文章工作流连通 - -**用户故事**:US3、US8、US9 - -### What to build - -将创作中心的新建/编辑操作与现有前台投稿工作台完整连通,保留 zibll 参考中的双栏创作层级,同时只复用 AKSRT 已有的编辑器、文章参数、封面、草稿、审核和付费配置能力。保存或提交后回到创作中心时保留上下文并反馈结果。 - -### Done when - -- [ ] 新建按钮使用 `aksrt_get_new_post_url()`,编辑按钮传递现有文章 ID,并由 `FrontSubmit::can_submit()`/`can_edit_post()` 控制可用性。 -- [ ] `page-submit.php` 的标题、编辑器、封面、分类、专题、高级筛选、标签、付费侧栏和操作区保持现有 AJAX、nonce 和权限链路。 -- [ ] 草稿保存、提交审核/发布、编辑已有文章和离开页面警告均继续由现有 `FrontSubmit` 与 `front-submit.js` 处理。 -- [ ] 从创作中心进入编辑器后返回时,能够恢复文章列表的状态筛选或分页上下文,并显示保存/提交结果。 -- [ ] 运行 PHP lint 及项目可执行的前端静态检查,确认本阶段未破坏现有投稿页面。 - ---- - -## Phase 3 — 文章价格表支持单篇与批量调价 - -**用户故事**:US4、US5、US6、US9 - -### What to build - -在创作中心增加文章价格表,支持免费/付费阅读/付费资源摘要、现金/积分、普通价格和 VIP 价格展示;实现单篇快速编辑及“选择文章 → 统一价格方案 → 预览 → 确认”的批量调价流程。必要时先抽取共享价格服务,再接入单篇和批量请求。 - -### Done when - -- [ ] 价格表按当前用户可管理文章展示免费、付费类型、现金/积分、普通价、VIP1/VIP2 及购买限制摘要;关闭付费时显示“免费”而不是误显示为 0。 -- [ ] 单篇编辑支持免费开关、支付模式、普通价、VIP1/VIP2、付费类型、购买限制和登录限制;不修改正文、封面或分类。 -- [ ] 单篇保存请求验证登录、作者归属、编辑能力和 `new_post_pay`,并复用 `FrontSubmitPay::save_pay_meta()` 或唯一共享价格服务。 -- [ ] 批量工具支持多选、免费/现金/积分价格方案、影响数量预览、确认和逐项成功/失败反馈;请求大小受控。 -- [ ] 最低/最高价、免费规则、VIP1/VIP2 层级、折扣计算、支付模式和付费类型校验与投稿页一致,且价格失败时保留输入值。 - ---- - -## Phase 4 — 移动端、可访问性与完整验收 - -**用户故事**:US7、US8、US9 - -### What to build - -完成创作中心文章列表和价格表的响应式布局、键盘交互、加载/空数据/失败/部分成功反馈,以及接口和安全边界验收。确保桌面端高密度工作台和移动端卡片/展开式编辑体验都可用。 - -### Done when - -- [ ] 在窄屏下文章列表和价格表转换为卡片或垂直字段布局,无页面级横向溢出,筛选、批量选择、编辑和确认操作均可通过触控完成。 -- [ ] 加载、空数据、请求失败和批量部分失败均有明确文本反馈;失败时不会显示误导性的 0 或清空用户输入。 -- [ ] 关键按钮、价格输入、状态标签和确认弹窗具备语义化结构、可见焦点和键盘操作能力,状态不只依赖颜色。 -- [ ] 所有新增 REST/AJAX 入口具备 `permission_callback` 或 nonce;输入使用项目既有清洗与价格归一化,输出完成转义。 -- [ ] `php tools/audit-admin-contracts.php` 退出码为 0 且无错误,并完成项目可执行的 PHP lint、前端构建或对应静态检查。 - ---- - -## Out of Scope - -- 不复制 zibll 的 PHP 函数、字段名、商品规格模型、订单模型或主题配置 API。 -- 不在用户中心新增独立的 `creator` 页面、第二套文章管理 tab 或第二套前台路由;`posts` 是唯一文章管理入口,界面标题可升级为“创作中心”。 -- 不把完整 TinyMCE 编辑器复制嵌入用户中心;新建/编辑继续跳转 `aksrt_get_new_post_url()` 对应的投稿页面。 -- 不修改文章购买、订单结算、收益分成、优惠码、付费下载实际业务规则。 -- 不新增旧选项双读、旧路由兼容层、数据迁移或新旧 UI 并存实现。 -- 不把日常创作工作台迁入配置型 Admin SPA。 - -## Open Questions - -- 默认将 `posts` 面板的显示标题改为“创作中心”,内部 tab key 保持 `posts`;若产品希望 URL 也改为 `creator`,需在实现前明确选择,因为项目规则不允许长期保留两个重复入口。 -- 默认批量调价采用“选择文章 → 统一价格方案 → 预览 → 确认”的模式;不包含按比例涨跌或价格模板市场。后续如需比例调整,应另立需求。 -- 默认价格表只覆盖安全可复用的价格字段;付费下载资源等复杂结构继续通过完整投稿编辑器维护,除非实现阶段确认现有服务可提供等价的批量契约。 -- 默认创作中心不内嵌完整编辑器;若需要无跳转编辑体验,应另行评估 TinyMCE 生命周期、自动保存和页面性能。 diff --git a/.specs/plans/user-creator-income-center.md b/.specs/plans/user-creator-income-center.md deleted file mode 100644 index 18ca7a6f..00000000 --- a/.specs/plans/user-creator-income-center.md +++ /dev/null @@ -1,136 +0,0 @@ -# Plan: 用户创作收益中心 - -## Architectural Decisions - -以下决策贯穿全部阶段: - -- **作者收益与推广返佣分离**:创作中心只读取付费文章作者收益,不读取 `Rebate` 推广返佣数据。 -- **收益记录以订单为源**:支付成功且权益发放完成后,每笔符合条件的付费订单最多生成一条作者收益记录。 -- **收益生成时快照**:保存原始支付金额、货币、作者分成比例和适用会员等级,后续配置或等级变化不影响历史记录。 -- **现金与积分彻底分离**:分别记录、统计、结算和写入流水;不换算、不合并,退款不改变作者收益。 -- **统一认证边界**:前台作者 ID 始终取当前登录用户,后台接口复用支付中心权限模型;所有 REST 路由必须提供 `permission_callback`。 -- **原子余额操作**:现金和积分余额只能通过现有余额服务变更,不直接修改用户 meta。 -- **最终结构替换**:新增或调整表结构时直接更新最终 schema 版本,不增加旧字段双读、迁移层或兼容路径。 -- **后台职责保持分层**:配置进入 Vue SPA 的“支付&付费 > 创作中心”,收益运营继续使用 Native 支付中心现有“创作分成”页面。 -- **创作开关是完整功能门控**:关闭后隐藏前台入口、停止新收益生成、停止自动和手动结算;历史记录仅保留后台查询。 - ---- - -## Phase 1 — 付费订单产生可追踪作者收益 - -**用户故事**:1、2、3、4、8、9、15 - -### What to build - -建立作者收益的最终数据结构和统一服务入口,接入支付成功且权益发放完成后的订单流程。实现普通用户、一级会员、二级会员三档比例,现金订单与积分订单分别生成收益记录,并保证支付回调重复执行时不会重复生成。 - -同时完成创作分成基础配置的后端读取与校验,使收益服务能够使用独立于其他会员分成配置的比例和功能开关。 - -### Done when - -- [x] `src/Core/DBMigratorPart2.php` 或最终负责收益表 schema 的文件包含作者、订单、文章、购买者、货币、原始支付金额、比例快照、会员等级、作者收益金额、状态和时间字段,并同步提升 schema 版本。 -- [x] `src/Payment/Revenue.php` 或拆分后的作者收益服务实现 `create_from_paid_order(order)`,且同一订单重复调用只保留一条作者收益记录。 -- [x] 现金订单仅创建现金收益记录,积分订单仅创建积分收益记录;作者购买自己的付费文章不会创建收益记录。 -- [x] 普通用户、一级会员、二级会员分别按 60%、70%、80% 计算,并把生成时比例保存到收益记录。 -- [x] 支付成功但权益未完成时不创建收益;权益完成后的支付流程能够创建待结算记录。 -- [x] 创作分成关闭时,收益生成入口被跳过;相关收益服务测试覆盖上述场景。 - ---- - -## Phase 2 — 收益周期结算与退款保持 - -**用户故事**:7、10、11、12、13、14 - -### What to build - -实现待结算收益按配置周期自动结算、后台手动逐笔/批量重试。现金收益写入现金余额,积分收益写入积分余额;退款或取消订单不再改变作者收益,所有入账操作具备幂等性。 - -### Done when - -- [x] `src/Payment/Revenue.php` 或拆分后的结算服务实现 `settle(record_ids, actor)`,现金和积分分别调用对应原子余额服务并写入对应流水。 -- [x] `settlement scheduler` 按站点时区处理达到配置结算周期的待结算记录;结算周期结束后自动入对应钱包,不再配置延迟小时或自动/手动模式。 -- [x] 同一收益被重复结算、并发结算或重复定时扫描时,只能成功入账一次。 -- [x] 订单退款或取消不会取消 pending 收益、冲正 settled 收益或产生待追缴记录;收益只按正常周期进入对应钱包。 -- [ ] 服务测试覆盖现金/积分比例快照、周期边界、自动结算、手动重试、退款后收益保持和并发幂等场景。 - ---- - -## Phase 3 — 用户中心作者收益页面可用 - -**用户故事**:1、2、3、4、5、6、7、15、16 - -### What to build - -将用户中心“创作中心”改造成作者收益页面。实现现金/积分双货币收益卡片、待结算与已结算展示、结算备注和三列收入明细,并保留基于现有用户中心 REST/Ajax 机制的分页加载。 - -保留独立的“收益中心”推广返佣入口,确保两个页面的数据和开关互不影响。 - -### Done when - -- [ ] `src/User/Center.php`、`inc/helpers/user-center-renderers/sidebar-commerce.php` 和相关侧栏逻辑在创作分成关闭时隐藏创作中心入口,但仍显示独立的收益中心入口。 -- [ ] 用户中心创作中心渲染器不再输出文章发布、草稿、审核、编辑、删除或文章管理内容。 -- [ ] `Creator income view model and renderers` 实现现金/积分今日收入、累计收益、待结算和已结算数据,列表只输出“现金收益/积分收益、+金额、待结算/已结算”。 -- [ ] `GET /wp-json/aksrt/v1/user-center/...` 作者收益接口只使用当前认证用户,未登录或访问他人数据时被拒绝;路由均具备 `permission_callback`。 -- [ ] 结算备注输出经过 `wp_kses` 处理的后台 HTML;收入明细默认每页 20 条、按产生时间倒序,移除货币 Tab 和状态筛选。 -- [ ] `assets/js/routes/user-center.js` 只转发创作中心分页参数并保持 History API 无刷新加载;请求失败时保留卡片和布局并显示可重试提示。 -- [ ] 前台交互测试验证现金/积分分栏、空状态、分页、退款后收益保持、移动端无横向溢出,以及创作中心与收益中心数据隔离。 - ---- - -## Phase 4 — 后台配置和支付中心运营闭环 - -**用户故事**:8、9、10、11、12 - -### What to build - -在“支付&付费 > 创作中心”集中提供开关、现金/积分两套三档分成比例、结算周期和结算备注文案 HTML。复用现有支付中心“创作分成”Native 运营页面,提供作者收益查询、筛选、逐笔/批量结算重试。 - -### Done when - -- [ ] `src/Admin/Schema/SchemaPayment.php`、`assets/admin/src/schema/sections/payment/index.js` 和支付菜单文件共同注册“创作中心”配置项。 -- [ ] 配置 REST 能加载和保存开关、两套 60/70/80 默认比例、结算周期和 HTML 备注;非法比例和周期值被拒绝。 -- [ ] `src/Settings/PaymentRuntime.php` 或对应 `AKSRT_Settings` 运行时读取器只读取新的创作中心配置,不增加旧配置双读或静默 fallback。 -- [ ] `src/Admin/PaymentController.php` 和现有 Native 支付运营页面支持按作者、文章、货币、收益状态、订单状态和时间查询收益记录。 -- [ ] 管理员可逐笔或批量结算,已处理记录不会重复入账;普通用户无法访问运营接口。 -- [ ] 后台只运营 pending/settled 收益记录;退款不提供作者收益冲正或追缴入口。 -- [ ] `php tools/audit-admin-contracts.php` 通过,后台配置与运营测试覆盖保存、权限、查询和操作结果。 - ---- - -## Phase 5 — 全链路验证与交付收口 - -**用户故事**:全部用户故事,重点覆盖异常、权限和部署契约 - -### What to build - -对收益生成、周期结算、退款保持、用户中心、后台配置和运营页面进行跨模块联调,完成静态检查、前端构建、契约审计和可用的 WordPress/外部 Docker harness 验证。修复跨阶段集成问题,并明确记录环境不可用时未执行的运行时验证。 - -### Done when - -- [ ] 收益服务测试覆盖现金/积分比例快照、货币分离、订单幂等、周期到期结算、退款后收益保持和并发重复操作。 -- [ ] 前台交互测试覆盖开关门控、本人数据隔离、双货币卡片、分页、错误重试、移动端布局和推广返佣隔离。 -- [ ] 后台配置与运营测试覆盖 Schema/REST 契约、权限、周期自动结算、批量结算重试和现金/积分查询。 -- [ ] `php tools/audit-admin-contracts.php` 返回成功状态。 -- [ ] `assets/admin` 执行 `npm run build` 成功,生成的 `assets/admin/build/` 文件保持可加载。 -- [ ] 按环境条件完成 PHP 静态检查和 WordPress/外部 Docker harness 运行验证;缺少运行环境时,在交付记录中明确列出未执行项及原因。 - ---- - -## Out of Scope - -- 本次不实现“我的收入”卡片提现按钮,也不新增提现申请、审核或支付流程。 -- 本次不改造或合并推广返佣收益中心。 -- 本次不恢复或迁移用户中心内的文章发布、草稿、审核、编辑、删除和文章管理入口。 -- 本次不实现按文章汇总的“我的商品”列表;收入明细固定按单笔作者收益记录展示。 -- 本次不提供作者收益导出、图表、趋势分析、排行榜或销售漏斗。 -- 本次不提供站内作者与购买者之间的订单争议处理流程。 -- 本次不允许现金与积分互相兑换、折算或合并统计。 -- 本次不设置作者收益进入钱包的 50 元结算门槛;该金额只用于现金提现提示。 -- 本次不实现退款后的作者收益冲正、余额扣回或追缴。 -- 本次不为旧收益结构增加兼容读取、双路实现或历史数据迁移。 -- 本次不新增独立后台收益工作台,后台运营继续复用现有支付中心“创作分成”页面。 -- 本次不引入新的前端框架、支付网关或货币体系。 - -## Open Questions - -暂无。PRD 中的收益口径、结算策略、货币处理、权限、退款保持和后台入口均已确定。 diff --git a/.specs/plans/user-promotion-center-inline-uiux.md b/.specs/plans/user-promotion-center-inline-uiux.md deleted file mode 100644 index e0fef24e..00000000 --- a/.specs/plans/user-promotion-center-inline-uiux.md +++ /dev/null @@ -1,68 +0,0 @@ -# Plan: 用户中心内链推广中心与 UI/UX 重设计 - -> 现状校正:邀请卡片当前**已不在**“账号设置”中,而是作为一个区块内嵌在“收益中心 / revenue”标签内 -> (`inc/helpers/user-center-renderers/profile-revenue.php:63` 依据 `show_invite_section` `require profile-invite.php`)。 -> 本计划按用户决定,将推广能力抽出为**独立的 `promotion` 标签**,并对其完成 UI/UX 重设计。 - -## Architectural Decisions - -Durable decisions that apply across all phases: - -- **独立标签键 `promotion`**:在 `src/User/Center.php` 面板注册表新增 `promotion` 面板;标签键与 URL 参数统一为 `promotion`,复用现有 `?tab=` 机制,不新增前台路由或页面模板。 -- **复用现有用户中心壳与渲染管线**:沿用 `page-profile.php` 壳、`aksrt_get_user_center_render_context()` 上下文、`templates/parts/user-center/panel.php` 面板包裹、侧栏组合器、`mobile-tabs.php` 移动导航与 `aksrt_get_user_center_tab_url()` URL 生成;只新增/移动渲染片段,不复制渲染路径。 -- **可见性沿用现有开关**:推广标签的注册与渲染沿用现有 `$invite_on` 判定(`credits_enabled()` && `invite_enabled()` && `invite_registration_enabled()`,见 `Center.php:130-133` 与 `aksrt-user-center.php:497-500`);任一关闭即不注册标签、不出现入口。 -- **深链归一到单一目标**:`invite` / `promotion` 深链统一解析到 `promotion` 标签(替换现有 `invite/promotion → revenue` 折叠);保留 `invite → promotion` 归一以满足 PRD “旧邀请深链归一”,不保留 revenue 双渲染路径。 -- **单一邀请渲染位置**:邀请卡片只在推广标签渲染;`revenue` 标签恢复为纯“收益与提现”,不再 `require profile-invite.php`。 -- **复用既有业务通路**:继续使用现有 REST 端点 `aksrt/v1/invite/me`、`aksrt/v1/invite/list`(`src/Credits/Invite.php:49,56`,登录用户权限)与前端 `assets/js/credits.js` 的 `initInvite()`(`:193`,`[data-aksrt-invite]` 契约);不新增邀请码生成、cookie、归因、注册绑定、奖励结算逻辑或第二套请求。 -- **样式模块化**:推广卡 `uc-promotion-*` 样式迁入独立 `assets/css/routes/user-center/promotion.css`,由 `assets/css/routes/user-center.css` 引入;从 `user-center/revenue.css` 移除,不引入独立 UI 框架。 - ---- - -## Phase 1 — 推广中心作为独立标签可端到端进入 - -**User stories**: US1、US4、US6、US7 - -### What to build - -将邀请能力从收益标签迁出为独立 `promotion` 标签的完整纵切:在面板注册表按现有邀请开关注册 `promotion` 面板;新增 `profile-promotion.php` 面板渲染器承接现有 `data-aksrt-invite` 卡片(本阶段保持行为一致,视觉重设计留待 Phase 2);从 `revenue` 标签移除邀请区块使其恢复为纯收益/提现;将 `invite`/`promotion` 深链与运行时别名统一归一到 `promotion`;在桌面侧栏与移动导航暴露推广入口。REST 与 `credits.js` 邀请逻辑保持不变。 - -### Done when - -- [ ] `src/User/Center.php` 注册 `promotion` 面板(`type=panel`),其注册受 `credits_enabled() && invite_enabled() && invite_registration_enabled()` 门控;三者任一关闭时 `get_dashboard_tabs()` 不含 `promotion`;`revenue` 面板的注册不再依赖 `$invite_on`。 -- [ ] 新增 `inc/helpers/user-center-renderers/profile-promotion.php` 输出含 `data-aksrt-invite` 的卡片;`aksrt-user-center.php` 的 `$built_in_tabs`(`:182`)与视图映射(`:147-162`)能将 `promotion` 解析到该文件;`profile-revenue.php` 不再 `require profile-invite.php`(`grep -n "profile-invite" inc/helpers/user-center-renderers/profile-revenue.php` 无结果)。 -- [ ] 深链归一:`aksrt_get_user_center_tab_url()`(`aksrt-user-center.php:106`)、`aksrt_user_center_resolve_tabs()`(`:289`)、`aksrt_user_center_runtime_config()` 的 `tabAliases`(`:347-350`)与 `inc/modules/user-parts/urls.php` 别名表(`:31-37`)均将 `invite`/`promotion` 归一到 `promotion`;访问 `?tab=invite` 服务端激活 `promotion` 面板(`is-active` 落在 `#panel-promotion`)。 -- [ ] 桌面侧栏(`sidebar-nav.php` 或 `sidebar-creator.php`)与 `mobile-tabs.php`(经 `dashboard_tabs` 自动渲染)暴露“推广中心”入口,其 `href` 来自 `aksrt_get_user_center_tab_url('promotion')`;`assets/js/credits.js` `initInvite()` 仍通过 `[data-aksrt-invite]` 绑定(该契约选择器未改名)。 -- [ ] 运行 `php tools/audit-admin-contracts.php`,退出码为 0 且报告 `errors=0`。 - ---- - -## Phase 2 — 推广中心完成 UI/UX 重设计与移动适配 - -**User stories**: US2、US3、US5 - -### What to build - -在独立推广标签内按“成果概览 → 分享操作 → 奖励说明 → 邀请记录”优先级重组信息架构并完成视觉重设计;把 `uc-promotion-*` 样式迁入独立 `promotion.css` 模块;确保加载中/空数据/请求失败的非误导状态反馈、键盘可操作的复制交互与可见成功/失败反馈、语义化结构与不只依赖颜色的状态;移动端单列、无横向溢出、复制按钮易触达。数据仍来自现有 `/invite/me`、`/invite/list`。 - -### Done when - -- [ ] 新增 `assets/css/routes/user-center/promotion.css` 承载 `uc-promotion-*` 规则并在 `assets/css/routes/user-center.css` 中 `@import`;`assets/css/routes/user-center/revenue.css` 不再包含 `uc-promotion-*` 规则(`grep -n "uc-promotion" assets/css/routes/user-center/revenue.css` 无结果)。 -- [ ] `profile-promotion.php` 面板按“成果概览 → 分享操作 → 奖励说明 → 邀请记录”顺序输出;复制按钮为可聚焦的 `