539 Commits
Author SHA1 Message Date
kate522andClaude Opus 5 fac4a714e0 feat(creator-studio): 前台 Gutenberg 创作工作台与受保护付费附件
前台投稿页改为沉浸式创作工作台:Gutenberg 负责区块解析与序列化,
React + TypeScript 负责工作台外壳与发布抽屉,Jio 服务端继续作为权限、
审核与付费的唯一权威。

- 新增 assets/creator-studio 独立构建(wp-scripts,锁定 WP 6.4 时代包),
  仅在投稿模板加载,构建缺失或游客访问时回退原有 TinyMCE 表单
- 自动保存(4s 防抖 + rev 冲突 409)、本地快照恢复、发布配置抽屉、
  移动端全屏单列与安全区适配
- 图片插入走现有 front-submit 上传管线,保留能力校验与待认领生命周期
- 已发布文章的改稿写入私有版本,审核通过前线上内容/链接/付费条款不变;
  原生后台「改稿审核」一次性应用或退回,并校验源文章并发指纹
- 私有附件新表 jio_private_asset + jio_dl_token.asset_id,schema 升到 3.4.0;
  文件存放 ABSPATH 之外,未配置私有存储时 fail closed
- 下载令牌绑定文章/附件/用户/IP/UA/次数,每次交付复核 Visibility 的
  订单、VIP 与配额判定;中断传输归还次数
- 付费字段改为写入前校验,非法价格不再留下半保存文章
- 新增 creator-studio 契约测试;更新受影响的 schema/下载/投稿契约

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-06 14:53:23 +08:00
kate522andClaude Opus 5 7a8279f8e7 fix(layout): pass term object to archive settings
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-06 00:00:17 +08:00
kate522andClaude Opus 5 fbb53c6890 feat(categories): align archive and directory pages
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 23:51:33 +08:00
kate522andClaude Opus 5 b124795d84 fix(links): use plus icon for logo upload placeholder
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 21:41:09 +08:00
kate522andClaude Opus 5 f53cdd4dc8 feat(links): align the friends page layout and submit UI
- assemble the friends template with page header, search cover, content position, widget zones and comments
- render the three page title styles and the tabbed search cover
- restore card, category nav and submit modal markup for the friends page
- add page content style support to the page editor
- port the linked card, category nav and submit modal styles to the mobile and desktop breakpoints

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 21:09:40 +08:00
kate522andClaude Opus 5 4c562428b4 feat(links): move friend links to native WordPress links with review flow
- enable the native Links manager and drop the custom friend-link option/REST path
- add per-page link settings for the friends template
- render link categories from get_bookmarks() with search and page-content options
- add a validated public link submission endpoint with logo upload, captcha, rate limiting and duplicate checks
- create pending links for review and notify administrators by email and in-app message
- add native Links admin pending count, columns and edit notes
- cover the new submission and cleanup contracts

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 19:54:33 +08:00
kate522andClaude Opus 5 8e74b60570 fix(navigation): let CSS size the nav logo and drop the inline width cap
The navbar logo carried an inline `height:36px;width:auto;max-width:{logo_width}px`
while `.navbar-logo > img` already owns `height:36px;padding:0 30px`. Under
`box-sizing:border-box` that max-width capped the border box, so the 60px of
horizontal padding stayed inside it and wide logos were squeezed against a
fixed height, breaking the intrinsic aspect ratio.

- Render the nav logo with no inline sizing; CSS alone sizes it.
- Add a `$lazy` argument to jio_get_adaptive_theme_img() so the navbar can emit
  the real src up front instead of a square lazyload placeholder; defaults to
  'auto' so every other caller keeps the lazy_other behaviour.
- Remove the superseded logo_width plumbing: the setting (PHP + VUE schemas),
  Settings::logo_width(), jio_get_site_logo_width(), the --jio-logo-width token
  and the unused has_logo context key.
- Cover the contract in the header reference test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 17:50:56 +08:00
kate522andClaude Opus 5 e6d9ff49a2 fix(navigation): align mega-menu rendering and menu-item backend with the reference
Renderer:
- Graphic-card images carry fit-cover so they crop into the fixed card box
  instead of stretching it and pushing the title out of alignment.
- Mega-menu link icons use fa-fw (fixed glyph column) instead of the
  undefined menu-item-icon class, so link titles line up.

Backend:
- Restore show_policy (login / vip / vip_2 / auth) on menu items; the field
  was missing entirely, so items could not be gated by identity.
- Default submenu_type to graphic_card, badge_class to jb-red, arrangement
  to swiper, size to md.
- Clamp img_scale to 20-300 to match the reference's spinner bounds.
- Drop the now-meaningless "none" submenu_type option: the senior-submenu
  switch is itself the on/off control.
- Admin JS gains the align control (left/center/space-around).

Cleanup:
- Remove the unused header_actions nav menu location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-05 10:51:36 +08:00
kate522 dcd306b428 feat(withdrawal): make payout channels a configurable sortable list
The payout channel was a two-item checkbox (alipay / wechat). Sites that pay
out through a bank transfer, USDT or any other route had no way to express it,
and every surface behind it — the withdrawal modal, the server-side check, the
admin review list — hard-coded those two names.

affiliate_payout_channels is now a repeater on both the PHP and Vue schemas,
using the same draggable component as the post filters and gateway config. Each
row carries a stable key, a display label, an account placeholder, an optional
note and an enabled flag. Disabled channels disappear from the front end but
stay resolvable, so historical withdrawal records still render their original
name.

PaymentRuntime::payout_channels() normalises the stored structure: it drops rows
with an empty key or label, de-duplicates keys, and falls back to the built-in
two channels when nothing is configured. enabled_payout_channels() feeds the
front end and payout_channel() resolves a single key for record display.
CommissionLedger's validation now compares against the configured, enabled keys
instead of a literal allowlist.

The withdrawal modal renders the configured list and swaps the account
placeholder and note when the channel changes; credits.js drives that. The
admin review page reads the same settings for its method labels and filter, and
the review dialog gained a "渠道说明" row so the operator sees the payout
instruction, hidden when empty.

Default channels live in SchemaPayment as plain data — the runtime imports them
rather than the reverse, because the schema layer must not depend on a runtime
settings class (doing it the other way made the schema harness fatal).

Verification: drove payout_channels() through default / custom / dirty / duplicate
inputs and confirmed enabled filtering, note lookup and disabled-channel
resolution. Added payment-withdrawal-channels-contract.test.cjs covering the
schema shape, runtime helpers, front-end wiring and admin read path. 873 tests
pass, PSR-4 audit clean, admin SPA builds.
2026-11-04 19:16:39 +08:00
kate522 6db34646ed feat(oauth): add the seven missing providers and open the full aggregate list
The theme shipped six direct providers while the reference carries fourteen,
and its aggregate-login channel offered three of sixteen platforms.

OAuthProviderClient now owns the providers the bundled SDK does not cover.
Douyin, Microsoft, Facebook, Twitter and Apple talk to their documented OAuth2
endpoints directly; Baidu and Alipay reuse yurunsoft, which already ships both
(Alipay needs its application private key, so provider_config learned an
extra_keys map to surface it). The broker dispatches through handles(), so the
existing start/callback routes stay provider-agnostic.

Three provider quirks needed handling rather than a plain copy:

Apple returns the callback as response_mode=form_post, and handle_callback
only read $_GET, so it now reads $_POST when the request is a POST — otherwise
every Apple login would fail with code_missing. Its client secret is built as
an ES256 JWT from Team ID / Key ID / .p8 rather than a static string.

Twitter requires PKCE. The verifier is derived from the state token, so it
travels with the existing transaction row and needs no extra storage.

Facebook hands back protocol-less avatar URLs, which are upgraded to https the
same way the OAuth avatar resolver does.

Settings gained matching toggles and credential groups on both sides — the PHP
schema and the Vue schema — and the aggregate platform list went from three to
sixteen, with CLoginClient's hard-coded allowlist replaced by the catalog
constant. Brand icons were extended so each new provider resolves to its
sprite; facebook and twitter moved to their colour sprites, and the
`twitter -> x` alias was dropped since it bypassed the new mapping.

The oauth-security contract previously asserted that Baidu and Alipay must not
appear on the OAuth surface. That was the old intent, so the assertion is
replaced with a positive one, keeping the separate check that the Alipay
payment gateway still exists.

Verification: a new harness stubs the HTTP layer and drives all five custom
clients — authorization URLs carry redirect/state (Twitter also a PKCE
challenge), identities normalize to the shared shape, Facebook's avatar is
upgraded to https, Apple's private-relay email survives, and an error payload
throws instead of yielding an empty identity. 871 widget/navigation tests pass
and the PSR-4 audit reports 0 errors.

Not covered: no live handshake happened — every provider needs real AppID and
secret pairs, and Apple/Douyin require developer-account review before the
full round trip can be exercised.
2026-11-04 18:00:29 +08:00
kate522 6f29e3d865 fix(oauth): actually use the third-party avatar and seed it only once
The OAuth flow wrote the provider avatar to `jio_oauth_avatar`, but nothing
ever read it — every user who signed up through GitHub/QQ saw a Gravatar hash
or the default avatar instead of their own picture. The reference seeds its
`custom_avatar` from the OAuth profile and its resolver reads it back, so both
halves were missing here.

jio_get_custom_avatar_url() now falls back to the stored OAuth avatar when no
uploaded one exists, which puts the chain at uploaded → third-party → external
service → default. The theme already swaps WordPress's get_avatar_data, so one
change covers every avatar surface.

Writes go through a single seed_avatar() guard: it no-ops when an uploaded
avatar or an existing OAuth avatar is present, matching the reference's
`if ($args['avatar'] && !$custom_avatar)`. Previously both the bind path and
the login refresh overwrote the meta unconditionally, which would have wiped a
user's own picture on their next social login.

Because the OAuth avatar now shows through once the upload is gone, both
"restore default" flows (the REST endpoint and the admin profile box) delete
it too; the admin checkbox also appears when only an OAuth avatar is set,
since $avatar_url is now non-empty in that case.

Baidu serves avatars from tb.himg.baidu.com, which 403s — rewritten to
himg.bdimg.com the way the reference does. On top of that the URL is upgraded
to https when the site runs over SSL, because the reference's scheme-relative
form still yields a plain http:// when copied verbatim, and browsers block it
as mixed content.

WeChatShare was reading `custom_avatar` — a reference-theme key this codebase
never writes — so its author branch always fell through to get_avatar_url();
it now uses the shared resolver like every other surface.

Verification: drove the resolver through five states with a stub (none /
OAuth only / both / upload only / Baidu legacy host) and confirmed the
expected URL each time. Added oauth-avatar-contract.test.cjs to pin the
resolution order, the seed guard, and the reset cleanup. 868 widget and
navigation tests pass; the PSR-4 path audit reports 0 errors.
2026-11-04 17:41:48 +08:00
kate522 7d78a33a9d fix(user-center): match the reference meta pills on the profile header card
The username and e-mail entries under the profile header rendered as plain
grey text on a solid icon. The reference makes each one a bordered pill that
copies its own value on click, drawn with the outline glyphs.

Both entries now carry `.but` plus `data-clipboard-tag` / `data-clipboard-text`
so the existing frontend clipboard runtime (assets/js/runtime/libs/clipboard.js)
picks them up and toasts "用户名已复制". Icons move from `fas` to `far`, which
is the FA5 equivalent of the reference's `fa-user-o` / `fa-envelope-o`.

Geometry follows `.author-header .user-identity > .but/.badg` exactly:
padding 2px 6px, margin 5px 6px 0 0, 12px type, 1.4 line height, and the meta
row gets the reference's 520px max width. medal.css already carried its own
approximation of this pill (2px 8px 2px 0, .3em .6em, 1.44) for the medal
entry; it now uses the same numbers so the two entries line up. The base rule
lives in layout.css rather than medal.css because the medal module is only
enqueued when badges are enabled, and the header must keep its shape either
way.

Verification: rendered the real header through a PHP stub and measured the
computed styles in the browser — padding 2px 6px, margin 5px 6px 0 0, 12px /
16.8px, 23px tall, both entries carrying the right clipboard payload. Added
user-center-header-meta-contract.test.cjs to pin the markup and geometry;
864 widget/navigation tests pass.
2026-11-04 17:27:51 +08:00
kate522 6d01e7489a fix(user-center): use the reference trend sprite for experience task rows
Each experience task row closed with a solid `fa-bolt` glyph. The reference
marks the same slot with the colour `trend-color` sprite followed by "+ N",
and uses that sprite for the 成长体系 heading as well, so the level page also
carried a stray `fa-chart-line` there.

The sprite already ships in the theme (`jiosvg-trend-color`); its path data is
byte-identical to the reference sheet. Both slots now render it, sized to
match the reference (`icon mr6 em09` for the rows, `icon mr6 em12` for the
heading). The "动态" rows keep their wording without a "+", which is how they
read before.

The check-in experience badges in src/Credits/Signin.php stay on
`fa-line-chart` — the reference uses the same monochrome glyph there.

Verification: rendered the real level page through a PHP stub — all ten task
rows emit `#icon-trend-color`, the only Font Awesome glyph left is the
"每日详情" arrow, matching the reference. Screenshotted against the real
stylesheet. A contract test pins both sprite usages and rejects a regression
to `fa-bolt` / `fa-chart-line`; 862 widget/navigation tests pass.
2026-11-04 17:17:49 +08:00
kate522 09b1e535e4 fix(user-center): use the reference colour sprites on the balance and points cards
Both asset cards opened with the same solid `fa-money-bill` glyph, so the
wallet and the points card showed an identical icon and neither matched the
reference, which pairs a blue wallet mark with the balance card and an orange
coin mark with the points card.

The theme already ships both sprites — the header wallet popover and the user
center sidebar resolve to `jiosvg-money-color-2` and `jiosvg-points-color`
through the same helper. Only these two cards were still on Font Awesome.
Swapping them keeps one icon family across the user center; the existing
`em12 mr6` sizing carries over unchanged because `svg` is 1em tall.

The wallet/points icon contract test now pins both keys and fails if either
card falls back to `fa-money-bill` or `fa-coins`.

Verification: rendered both cards through a PHP stub and screenshotted them
against the real theme stylesheet — the blue wallet and orange coin render at
the same optical size as the labels. 861 widget/navigation tests pass.
2026-11-04 17:08:09 +08:00
kate522 00ae62f62b fix(auth): close the modal regressions and keep the header component off /sign/
Three defects slipped into the sign-card rebuild:

The hidden-glyph rule was dropped. `.fa` sets display:inline-block, which
outranks the UA [hidden] rule, so every hidden glyph stayed visible — most
visibly a spinner rendered beside the login button at rest. `.sign [hidden]`
restores the override.

Closing account login produced an empty shell. The whole sign-in pane was
gated on `!$close_signin`, and the social block carried the same gate, so a
site with account login off but registration on still opened on an empty
panel with no social entry. The pane is now rendered in three shapes: open
registration falls through to the signup tab, a QR entry falls through to
the QR tab, social-only renders a notice plus the social bar, and when
nothing is available it states that login is closed.

The modal close button sat in normal flow and pushed the card content down;
`#u_sign .sign > .close` pins it back to the top-right corner.

The /sign/ route is `index.php?jio_sign=1`, so WordPress reports it as home.
The header multifunction component resolves page type by query flags and the
home branch won, so the component appeared on the auth page whenever "首页"
was in its scope — even though the sign page template itself was already
excluded. `jio_header_slide_current_page_type()` now bails out on `jio_sign`
before any of the is_* checks.

The breakpoint overrides that were fighting the shared sheet between 641px
and 991px are narrowed to the one offset that actually needed adjusting.

Verification: the modal was rendered through a PHP stub, served with the real
theme stylesheets, and driven by the real auth.js in a headless browser —
tab switching, signin-mode switching, password reveal, close button and
geometry at 1440/1100/900/760/500/390px. The header harness gained a
`jio_sign` case asserting no markup, and 860 widget/navigation tests pass.
2026-11-04 16:51:41 +08:00
kate522 41366e7fc4 feat(auth): rebuild the sign-in surfaces on the shared sign-card contract
The login/register page and the auth modal had drifted into a parallel
system: floating-input fields, a sign-page-card shell and a bespoke
jio-auth-modal wrapper that none of the theme's own sign CSS targeted.
The reference card rules (.sign, .sign-img, .sign-content, .line-form-input,
.scale-placeholder, .social_loginbar, .title-h-left, #u_sign .modal-dialog,
.qrcode-signin-container) have been in main.css all along, so the fix is to
render the reference DOM instead of re-styling a second one.

auth-content.php now emits one .sign.jio-panel.blur-bg.relative card with a
.tab-content of #tab-sign-in / #tab-sign-up / #tab-resetpassword /
#tab-qrcode-signin / #tab-bind panes. Each pane opens with a
.title-h-left.fa-2x heading plus a .muted-color.px12 link, fields are
.relative.line-form.mb10 > .line-form-input + .scale-placeholder +
.line-form-line, the verification trigger is the .yztx.abs-right captchsubmit
button, and submits are .but.radius.jb-blue|jb-green.padding-lg.btn-block.
Social entries come from the shared render_social_buttons() separator +
.social_loginbar, and the OAuth bind pane uses the oauth-data-box avatar card.

The modal shell is #u_sign.modal > .modal-dialog > .sign-content >
.sign-img.absolute.hide-sm + .sign, and the page shell is the reference
.container.sign-page > .row.sign-row.gutters-5 > .col-md-6(.col-md-offset-6/
.col-md-offset-3).sign-col with a .fixed.lazyload background and a blurred
fixed footer. auth.js drives .in instead of .is-open, toggles the new
password/signin-mode/invite-gate controls and no longer touches the retired
markup. The page also drops two footer-hook removals that targeted functions
which no longer exist, and opts out of the fixed-nav offset.

The sign page's custom theme setting now actually applies: Router answers
jio_forced_theme_mode, jioData.themeForced wins over the stored cookie in
theme.js, theme-toggle positions are suppressed, and the page marks html/body
dark or white accordingly.

sign.css shrinks from ~114 lines of bespoke component styling to a thin
positioning layer, and the retired classes are gone rather than kept.

Verification: php -l on every changed PHP file, node --check on both JS files,
node --test across widget-system, navigation and front-submit (873 passing),
and tools/audit-psr4-paths.php / audit-admin-contracts.php both at 0 errors.
assets/js/runtime/main.min.js was rebuilt because theme.js gained the forced
theme branch.
2026-11-04 16:04:24 +08:00
kate522andClaude Fable 5 d40906b4a3 feat(single): align author bar follow and reward actions with the site flows
- guests get the sign-in trigger instead of a redirect to the login page,
  for both follow and wallet reward controls
- follow requests swap the label for an inline loader and block a second
  submit; the followed state uses the .actived hook
- follower counts sync through data-follow-count on the user homepage
  (the previous selector matched nothing) and only for the viewed profile
- reward button uses the secondary action colour; the reward template is
  only rendered for signed-in visitors
- drop the unused author-hero aria-disabled rule and stale wording in the
  touched comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-11-04 13:34:34 +08:00
kate522andClaude Opus 5 02246c7ec8 fix(nav): render the theme toggle with the outline sun/moon face
The top-bar theme switch looked heavier and larger than the icons beside it.
An earlier commit dropped the `far` modifier from fa-toggle-theme, which moved
the glyphs to the Solid face. The reference's glyphs are the outline variants
(FA4's sun-o / moon-o), and in FA5 those live in the Regular face — so the
Solid replacement reads bolder and visually bigger at the same 1em size, while
the neighbouring account icons stay on the lighter Regular-style SVG set.

Restore `fa far fa-toggle-theme` on all three placements: desktop navbar,
mobile header, and the floating rail. The .fa-toggle-theme::before/::after
composite rules only handle positioning and the slide transition, so they are
unaffected by the face swap.

legacy-navigation-contract already asserts `fa far fa-toggle-theme` and passes
again; full suite 999/1000 with the pre-existing frontend-lifecycle failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-03 19:58:54 +08:00
kate522andClaude Opus 5 93e83e1835 fix(single): align article like/favorite interaction UX with reference
The like and favorite buttons gave no in-flight feedback: the label just sat
there until the request returned, the lovestar animation never replayed on a
repeat like, duplicate clicks reported "success", and guest favorites silently
wrote a cookie instead of asking the user to sign in.

- pending state: replace the count with the reference inline spinner
  (`<i class="loading zts">`) and disable the button until the response lands
- animation: strip `action-animation` before re-adding it (forced reflow), so
  every successful like replays the reference lovestar keyframes
- duplicate: when the server reports the same state and count, skip the
  animation and surface the reference warning copy (已赞过此文章了!)
- failures: restore the original count and toast `danger`; guest-only actions
  now open the site login modal instead of dead-ending on an error
- favorite requires login server-side, matching the reference; the guest
  favourite cookie branch and its reader are removed as dead code
- unify like/favorite through one bindToggle path and keep every mirror
  button (article row + footer tabbar) in sync

Tests: new article-actions-ux suite (8 cases) drives assets/js/core/article.js
against a DOM/fetch shim — pending spinner, replay, duplicate no-op,
concurrency, rollback, guest login, mirror sync. Widget suite 828/829; the one
failure (frontend-lifecycle theme-init stub) predates this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-03 16:36:45 +08:00
kate522andClaude Opus 5 f80ca1e27d fix(widgets): restore single-card reference UI for search and quote widgets
The 搜索框 and 一言 sidebar widgets fell into the generic renderer branch, so
they rendered a .jio-panel card wrapping a second bespoke card
(.jio-widget-search / .jio-widget-quote). In-article 一言 also used
divergent modifier classes and an extra hover rule.

- search: render through the shared jio_get_search_box() (legacy keyword,
  history and category pickers) inside one reference card, mirroring the
  reference widget's markup and title default 「搜索」
- quote: render the reference .yiyan-box card itself as the click root; the
  shell now emits only the reference widget-container/title wrapper
- article 一言: adopt the reference class lists for header, footer and the
  standalone after-article box; drop the invented yiyan--* modifiers and
  the duplicate hover/opacity rules
- client: fill .cn/.en in the reference order (first line .cn, second .en)
  and drop the stale window.JioYiyan bridge plus the dead qv-yiyan.php AJAX
  path in runtime main.js
- schema: replace the ad-hoc search_style radio with the reference switches
  (keywords, history, input category, more_cats) and default title
- assets: enqueue assets/js/yiyan.js through the quote widget's asset list

Tests: widget-system suite 820/821 (the one failure is the pre-existing
frontend-lifecycle theme-init stub); new search/quote card contracts pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-03 15:56:52 +08:00
kate522andClaude Opus 5 030baa511e fix(nav): restore reference theme toggle geometry and single binding
The top-bar theme switch was bound twice — core/theme.js attached a click
handler to #theme-toggle-desktop while runtime/main.js delegated click on
.toggle-theme, and both ship in main.min.js, so one press toggled twice and
the button looked dead.

Also realign the control with the reference:

- Drop the far modifier from fa-toggle-theme so the sun/moon glyphs resolve
  against the Solid face at the reference weight instead of Regular.
- Restore .toggle-radius .fa/.icon to the reference form (left/right 0 +
  auto margin only). The extra position/top/transform/size/flex block and
  the > i selector were overriding the shared abs-* centering rule.
- Replace the per-ID bindings with one delegated listener so the floating
  rail, which carries no id, keeps working.
- Remove dead theme.js queries for selectors no markup or stylesheet ships
  (#theme-toggle, #dropdown-theme-toggle, .theme-icon-moon/.theme-icon-sun,
  [data-action='toggle-theme'], .is-dark).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 22:24:20 +08:00
kate522andClaude Opus 5 427968a721 chore(branding): remove leftover upstream theme identifiers
The theme was renamed Zibll/子比 → AKSRT → Jio, but upstream branding
survived the sweeps in CSS classes, the SVG sprite prefix, a persisted
widget field ID, JS events, admin Vue class names, test infrastructure
and docblocks.

Renames:
- zib-widget / zib-widget-wrap / zib-slider → jio-widget-box /
  jio-widget-box-wrap / jio-slider-box (jio-widget is taken by the modern
  widget shell, jio-box-* by the card primitives)
- zib-partners-scroll + zps-* → jio-partners-scroll + jps-*
- zib-combo-info-card + zci-* → jio-combo-info-card + jci-*
- zibsvg- → jiosvg-, including the exported picker helpers and the
  fa-zibsvg- family-prefix guard
- zibpay_type → pay_type; the dual-read fallback in PostQuery collapses
  to a single key
- [zibajax="submit"] dropped (no PHP emitter); zib_ajax.success →
  jio_ajax.success
- aks-* admin Vue classes → jio-*
- zib_palette() → palette()

User-facing vendor traces are gone: the floating "more" button default no
longer ships a zibll.com link or 子比主题 marketing copy, the notice
widget ships no preset promo entries, and the four 官网教程 links are
removed from the post-list admin schema.

Test infrastructure renamed to neutral names (docker-compose.reference.yml,
Caddyfile, article-baseline.json, modern-widgets-contract.test.cjs) with
their DB/volume/env/slug identifiers following.

Kept intentionally, documented in tests/fixtures/README.md: the golden
fixture reference fingerprint (a SHA-256 record of an external directory,
read by validate-widget-golden / generate-widget-golden-fixture /
audit-widget-system), the doesNotMatch guards that prove the old tokens are
gone, and the reference theme's own meta key in the visual fixtures.

No backward-compat or data migration: saved widget configs holding old
icon keys or the old field ID silently lose them, per the project's
rewrite-over-compatibility rule.

Verified: 132/132 node contract tests, php tests/widget-system/run.php
(146 assertions), audit-admin-contracts, audit-psr4-paths and
validate-widget-golden all green; tracked bundles regenerated and in sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 00:06:54 +08:00
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00
kate522andClaude Opus 5 5b16bc6e15 fix(frontend): route the VIP points-exchange entry through the custom currency name
/user/vip/ 档位购买卡下方的入口横条仍硬编码「积分兑换会员」,而后台
「积分配置 → 自定义货币名称」改名后不跟随。上一轮改名只覆盖了兑换弹窗
标题与会员弹窗内入口,页面自身的入口横条是另一处服务端渲染,漏改了。

入口横条(inc/helpers/aksrt-vip.php)
- aksrt_get_vip_points_exchange_entry_html 改取已加载的
  catalog.currency_name,回退 Settings::get_currency_name() 再回退「积分」

同一页面上其余漏改的兑换文案一并收口,命名与入口/弹窗保持一致:
- VipServiceModal:积分不足提示 → {货币名}不足,暂时无法兑换
- VipServicePurchase.exchange_with_points:兑换未启用 / 兑换无效 /
  不足三处 WP_Error、流水说明、订单备注(%1$s兑换会员 %2$d %1$s。)
  currency_name 在此单独解析,get_catalog 内的局部变量不在该作用域
- LedgerPresentation:vip_points_exchange 流水原因标签

契约测试新增源断言,并用两个 PHP harness 做运行时断言:入口横条
(改名后 / 已是会员不输出 / 缺 key 回退)与弹窗(余额充足 / 不足两分支)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 16:19:49 +08:00
kate522andClaude Opus 5 5c6b422866 fix(frontend): route every user-center points label through the custom currency name
后台「积分配置 → 自定义货币名称」改名后,用户中心仍有大量硬编码
「积分」:做任务赚积分 / 购买积分 / 签到领积分、积分记录 / 积分任务、
每日可免费获取 XX 积分、我的积分、积分+1、创作中心积分收益等。

get_currency_name() 早已存在且可用,问题只是这些文案没有走它。
本PR 全部替换,无双路、无迁移:

服务端渲染(用户中心 renderers)
- credits-balance.php:购买/做任务赚/签到领 + 整个购买弹窗
- profile-credits.php:积分记录 / 积分任务 页签
- credits-points-tasks.php:每日上限三态与空态
- sidebar-creator.php / profile-creator.php:积分收益
- transfer-modal.php:积分转账提示
- Signin.php:签到详情弹窗(我的积分 / 奖励 tooltip / 积分+N)
- VipServiceModal.php:我的积分 / 积分兑换会员
- download-permission-card.php:购买价格

客户端渲染(JS 无法调用 PHP,注入 currencyName 到 aksrtData)
- signin.js:签到状态行「已签到 N 天 +1」
- credits.js:转账标题/金额标签 + 购买提示 toast
- cms-download.js:积分不足视图、我的积分 入口、余额不足文案
- pay-vip.js:积分兑换会员入口(优先 catalog.currency_name)
- order-center.js:订单行单位回退

契约测试同步改为断言模板构造而非死字符串,并新增两例:
改名后跟随、未注入时回退「积分」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 15:09:49 +08:00
kate522andClaude Fable 5 48e54dc115 fix(tests): restore green contract suite
Drop the legacy credits_added message label per the hard-cut rule, switch
three remaining Font Awesome 4 icons to FA5 solid, and align two widget
contracts with the badge rule grouping and renderer list from fc04bf65.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-31 14:18:01 +08:00
kate522andClaude Fable 5 80aecab586 feat(frontend): upgrade report dialog experience
Replace manual report links with automatic target association, improve validation and evidence uploads, and remove retired report UI code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-30 23:56:59 +08:00
kate522 4ea00f1322 fix(frontend): unify article timestamp clock icons 2026-10-29 23:13:14 +08:00
kate522 4cdde50695 fix(frontend): align post thumbnail display with reference 2026-10-29 23:07:08 +08:00
kate522 542daca457 fix(frontend): align search and header components with reference 2026-10-29 22:21:01 +08:00
kate522 985f18c750 fix(widgets): align mini paid badges with reference 2026-10-29 21:15:11 +08:00
kate522 fd034ff055 fix(widgets): align tabbed post layouts with reference 2026-10-29 20:31:39 +08:00
kate522 8aa116ec43 fix(theme): align reference layouts and restore passing contracts
Align homepage and category discovery, modern widgets, admin controls, quotes, and comments with the reference theme. Remove superseded paths and add rendering and behavior coverage. Update stale asset and finance contracts; all 760 Node tests and 139 widget assertions pass.
2026-10-29 19:16:03 +08:00
kate522 b520d29368 fix(frontend): match navigation icon divider spacing 2026-10-29 18:17:51 +08:00
kate522 9b27508a5b fix(frontend): align top navigation with reference theme 2026-10-29 18:10:00 +08:00
kate522 1c501ff713 fix(user): align badge modals with reference theme 2026-10-29 17:38:10 +08:00
kate522 8e9d3a7e49 fix(signin): align check-in feedback with reference theme 2026-10-29 17:06:25 +08:00
kate522andClaude Fable 5 d9d1122e9c feat(pay): add wait-pay float window aligned with reference theme
- Render pending orders (max 5, >=3 min left) in a top-right float button
  with hover list and countdown, gated by pay_wait_float_s
- Fix order countdown parsing expires_at as UTC
- Drop unused .paid-modal-lists-box thumb rule

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-29 10:45:23 +08:00
kate522andClaude Fable 5 7e86f2a821 用户中心侧栏工单中心图标改用 comment-color
- sidebar-nav: 工单中心 icon ticket-color -> comment-color
- 删除 assets/icons/sprite/ticket-color.svg 并重建 svg-icon.js
- 同步更新相关契约测试

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-29 10:10:49 +08:00
kate522andClaude Fable 5 54ea83b262 feat(icons): split built-in sprite into per-icon SVG sources and add ticket icon
Move the 114 built-in sprite icons out of assets/js/runtime/svg-icon.js into
assets/icons/sprite/{key}.svg and generate svg-icon.js with
tools/build-svg-sprite.mjs. Add ticket-color and use it for the user center
ticket entry. Update sidebar contract tests for the creator card labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 23:59:06 +08:00
kate522andClaude Fable 5 794d981260 revert(user-center): restore promotion center ratio label
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 23:41:37 +08:00
kate522andClaude Fable 5 52749bf232 feat(user-center): rename promotion center ratio label to commission ratio
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 23:40:41 +08:00
kate522andClaude Fable 5 72e55d95fd feat(user-center): show cash and points income on sidebar creator card
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 23:39:01 +08:00
kate522andClaude Fable 5 c152e00501 fix(user-center): 1:1 port reference sidebar statistics row and icon box line-height
- Rewrite sidebar stats as a single zib-widget row (user-statistics-item, em14 / em09 opacity5 mt6), drop legacy .uc-stats styles
- Restore reference body line-height (1.42857143) on stats and service/settings boxes
- Move 我的订单 to the front of 我的服务 to match reference order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 22:48:54 +08:00
kate522andClaude Fable 5 6ded62df92 fix(user-center): 1:1 port reference sidebar service/settings icon boxes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 22:10:45 +08:00
kate522andClaude Fable 5 9fbfab54c2 feat(user-center): 1:1 port reference sidebar VIP bar and creator/promotion cards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 22:01:37 +08:00
kate522andClaude Fable 5 57ff405691 refactor(color): unify color sanitizing and picking behind shared components
- Add AKSRT\Core\Color::sanitize($value, $allow_alpha) as the single PHP
  color sanitizer; replace sanitize_hex_color calls and per-class regexes
  (FloatButtons, FieldSchema, ThemeSearch, icon cards, TicketStatusService)
- SchemaProvider / ControllerBase sanitize `color` fields as colors;
  alpha kept only when the schema sets showAlpha
- Split SchemaProvider OAuth validation and option matching into sibling
  traits to stay under the 1000-line limit
- Add AksColorPicker.vue + utils/color.js mirroring the PHP rules; AksField,
  FloatBtnCommonFields and TicketStatusPanel use it
- Widget background color input now uses wpColorPicker
- Remove dead AksCompositeField dimensions/spacing/background branches and
  the unused Gutenberg ColorPicker var
- Add color-unification contract test; update harness requires and tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 20:47:41 +08:00
kate522andClaude Fable 5 3bd15feccb feat(ticket): redesign user-center ticket center UI/UX
- Status stat cards double as quick filters; debounced search with stale-response guard
- Card list with status stripe, windowed pagination, skeleton/empty/error states
- Detail dialog as chat timeline with in-place refresh, quote preview, Ctrl+Enter send
- Create form with department chips, inline validation, multi-image upload, success notice
- Split shared helpers into ticket-center-core.js and enqueue it before the panel script
- Drop dead data-aksrt-ticket-config and legacy colorful modal header

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-26 23:06:34 +08:00
kate522andClaude Opus 5 dbb7de41e9 feat(ticket): replace the after-sale flow with a general ticket system
The old 售后 flow was an order-refund appendage: tickets were hard-bound to an
order number, users could only file them from 「我的订单」against a paid order,
and the admin action bottomed out at 「标记订单已退款」. It could not carry
account questions or feature requests that have nothing to do with an order.

Replace it with a general ticket system modelled on 魔方财务云's idcsmart_ticket:
title + department + description are required, linking an order is optional
(user's own paid orders only). Add departments, custom statuses, preset replies
and internal admin notes as four configuration surfaces. Conversation history
moves off the ticket row's JSON log into dedicated ticket_reply rows, which
unlocks quoted replies. Front end gets a 工单中心 panel under 我的服务; back end
moves from 支付中心 → 售后服务 to users.php → 工单管理.

Per CLAUDE.md's rewrite-over-compatibility rule: drop the old table, build new
ones, delete the old code. No dual paths, no data migration. The upstream
plugin's upstream/downstream delivery and internal-ticket conversion are
deliberately left out — both are multi-provider platform concerns with no
meaning on a single WordPress site.

Data: order_aftersale becomes six aksrt_ticket* tables (ticket, ticket_reply,
ticket_status, ticket_type, ticket_prereply, ticket_note). SCHEMA_VERSION
2.6.0 -> 3.0.0. Status and department seeds are idempotent; the default status
「待接单」is flagged as non-editable.

Domain: new src/Ticket/ (13 files, all under 460 lines). Attachments reuse
UserController::rest_upload_comment_image with a ticket-specific permission
callback, mirroring the existing /report/upload-image route rather than
growing a second upload path.

Notifications go through AKSRT\Messages\Message::send() and deep-link to the
right surface; email/SMS stay out of scope since the theme's mail and SMS
stacks are separate implementations.

Refund gate opens up: marking an order refunded no longer requires an in-flight
ticket, only that the order is paid.

Also generalise a hardcoded special case in tools/audit-admin-contracts.php —
schema domain inference now reads the parent directory instead of matching
"PostList" by name, so new leaf schema files need no per-name exception.

Verification:
  php tools/audit-admin-contracts.php   exit 0, errors=0
  node --test (widget-system + admin)   664 pass / 0 fail
  php -l over all new/changed files     clean
  node --check over all JS              clean
  npm run build + build:public          clean

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-25 23:29:51 +08:00
kate522andClaude Opus 5 761c84d70f feat(orders): replace order stat cards with a 售后中 filter
/user/orders 顶部的「已退款」「售后待处理」统计卡移除(统计行改为
全部 / 待支付 / 已支付 / 已关闭 四格,栅格同步由 3 列调为 4 列),
筛选的订单状态新增「售后中」,命中带有「待审核 / 处理中」工单的订单。

售后中与订单自身状态正交(售后中的订单可能仍是 paid,也可能已
refunded),因此不过滤 status 列,而是由 OrderCenterQuery 通过
repository_args 的 after_sale_open 下传,在 get_user_order_query_parts
中作为独立谓词叠加工单子查询,与类型、订单号条件自然组合。

工单流转口径复用 AfterSaleService 的 STATUS_PENDING / STATUS_PROCESSING,
与 find_active() 的退款闸门同一套判定;已完结(completed)与已撤销
(cancelled)不算售后中。

同时清理随之失效的死代码:UserOrders::get_user_order_stats()、
AfterSaleService::count_user_open()、aksrt_get_order_status_labels(),
以及两处 summary 查询与空视图中的 refunded / after_sale_pending 统计键。
不写双路与数据迁移,旧键直接替换。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-25 18:58:06 +08:00