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.
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.
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.
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.
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.
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.
- 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>
The article body ran two highlighting pipelines at once: the runtime
EnlighterJS pass (same one the comment area uses) plus a bespoke
precode.js enhancer, and a hand-rolled .code-block skin. The two
disagreed, so rendered blocks carried a dark #282c34 card with 15px
padding, 14px text, a border and a second copy button stacked on top of
the EnlighterJS toolbar.
Drop the parallel path and let one pipeline own code blocks:
- delete precode.js, single-post/code-blocks.css and the unused
assets/js/enlighter copy that only precode.js referenced
- drop the now-dead has_code requirement (PHP scan + feature enqueue)
- render pre exactly as the reference does: bootstrap's pre box
(9.5px padding / 13px / radius 4) plus main.css's muted background,
with the EnlighterJS theme supplying the skin
Also scope the inline-code skin to :not(pre code) so it stops painting
the block background pink, and remap the toolbar copy glyph: \f0ea is
the clipboard in FA4 but "paste" in FA5; the clipboard is \f328.
Verified against the reference at 1440px both themes: pre 13px/18.57,
9.5px padding, radius 4, muted background; EnlighterJS block heights
151.31 / 60.38 match exactly.
Measured in the live header: the toggle's glyph was 15.25px tall centred at
y=31.5, while the account and search icons are 14px tall centred at y=34.09.
Two causes stack up:
- `.toggle-radius` is a fixed 29x29 circle with no font-size, so the sun/moon
pseudo-elements inherit `.navbar-top`'s 15px. The sibling `.jio-nav-icon`
buttons pin 14px, so the toggle glyph renders a size larger.
- The toggle sits alone in a 29px `.navbar-form`, while the account cluster is
34px (6px padding + 20px line box + 6px). Both clusters are top-aligned, so
their centres differ by 2.5px.
Pin the toggle's glyph to 14px and give its cluster the same 34px flex line as
its neighbours. Scoped to `.navbar-top`, so the mobile drawer and the floating
rail (which also carry .toggle-theme) keep their own geometry.
Verified via headless Edge + CDP on https://localhost: glyph now 14px, centre
delta 0.09px (was 2.5px); pixel measurement shows the sun at 14.0px against
14.0px for the search icon.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
The 代码高亮 and 系统公告 schemas existed but appeared in no menu, so their
routes were never generated (routes come from the sidebar constants). The
reference keeps both inside the 常用功能 panel rather than as their own
entries, so follow that structure instead of adding sidebar items.
- global/common now carries three groups after the lazy-load ones:
代码高亮 (switch / line numbers / buttons / max height / light+dark theme)
and 弹窗通知 (policy, content, 4 buttons, size, radius, icon, expiry)
- group keys are uniquified (code-highlight, code-theme, notice-*) so they do
not collide with the existing performance group
- delete the orphan code-highlight.js / system-notice.js schemas and their
section entries on both the Vue and PHP sides
- delete post-list/pay.js and its PHP counterpart: every post_pay_* and
post_download_* key there had zero runtime consumers, and the reference has
no such page (paid-content config lives under 支付&付费)
tools/health and tools/import-export turned out to already be reachable as
custom pages, and remain untouched.
Tests: admin contract audit 0 errors (warnings 56 → 51); SPA builds clean;
suite 967/968 with the pre-existing frontend-lifecycle failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment [code] blocks rendered as bare <pre><code> with only Bootstrap's
grey box — no syntax colours, unlike the reference.
The runtime gates EnlighterJS on `_win.highlight_kg`, which was hardcoded to
false. With that false the selector degrades to `pre code.gl, pre code.special`,
and comment code carries neither class, so it never highlighted. The admin
already exposes 「全局功能 → 代码高亮」 (default on), and jioData.codeHighlight
reads it — only the runtime payload disagreed.
- _win.highlight_kg / highlight_hh / highlight_zt (plus white/dark theme
names) now come from Settings::get_code_highlight_config(); the initial
theme follows cookie → mode → time-auto via initial_theme_mode()
- define those values before the _win localize (it sits above jioData in the
same method)
- inject the reference's two head rules the runtime path was missing:
max-height from `highlight_maxheight`, and
`.enlighter-toolbar{display:none}` when `highlight_btn` is off
Tests: comment-image-upload contract gains cases pinning the settings-backed
booleans (and their definition order) and the two inline CSS rules; suite
837/838 with the pre-existing frontend-lifecycle failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
While uploading, the confirm button turned into a thin 3px bar below the
label instead of the reference's striped overlay that fills the button.
Two causes:
- the markup used <span class="progress">; Bootstrap sizes the bar with a
block-level .progress, so the swap produced a flattened line
- shell.css carried a bespoke override pinning .progress to
`position: static; height: 3px`, discarding the absolute full-size
overlay that main.css already provides
Changes:
- emit the reference skeleton: <div class="progress progress-striped active">
> <div class="progress-bar progress-bar-success">, so the striped bar
spans the button and the gradient shows through the translucent layer
- give the upload button `position: relative` so the overlay is contained by
the button rather than the dropdown panel; delete the static-line override
- restore the reference's "正在处理请稍等..." toast and switch the label to
"处理中 ..." at 100%, both keyed to the shared `miniupload_ajax` toast id
so the completion message replaces the loading one
- notify() now forwards the toast id instead of dropping it
Tests: comment-image-upload contract gains two cases pinning the div-based
striped overlay and the deletion of the 3px override; suite 835/836 with the
pre-existing frontend-lifecycle failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The publish time under the author name diverged from the reference in four
ways: it rendered a leading bullet from `.icon-circle`, its tooltip opened
above instead of below, its wording was "2 天发布" (WordPress'
human_time_diff has no 前 and keeps spaces), and it ignored the
「时间显示数据源」 setting entirely.
- markup: drop `.icon-circle` from the time span and the description fallback;
add `data-placement="bottom"` so the tooltip opens below like the reference
- wording: use jio_time_ago() (which already ends in 前) and append only
发布 / 更新, yielding "2天前发布"
- branches: mirror zib_get_post_time_tooltip exactly (date source → publish
time; unpublished → modified time; published-with-update → modified time
labelled 更新 while the tooltip keeps the publish time; otherwise publish)
- the setting is read from post_time_source via get_post_list_style_settings()
Tests: new author-bar-time suite drives the real template through a stub
harness across all four branches (4/4); widget suite 833/834 with the
pre-existing frontend-lifecycle failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With a featured image the breadcrumb sits under the title inside the cover
overlay, but its first icon started 6px left of the title's left edge.
The overlay rule carried an extra `padding: 0` that this theme added on top of
the reference. The same node also matches `ul.breadcrumb { padding: 0 }`
(0,1,1 specificity), which outranks the base `.breadcrumb { padding: 0 8px }`
(0,1,0) — so the reference's `margin-left: -6px` nudge was applied with no
inset to compensate, pulling the whole row 6px to the left.
Writing the inset back as `padding: 0 8px` on the overlay rule restores the
reference geometry: -6px + 8px nets +2px from the panel edge. The global
`ul.breadcrumb` reset and the video footer variant are untouched, so archive
and video breadcrumbs keep their current padding.
Tests: new case in the single-cover contract suite pins the 8px inset (and the
global reset); cover suite 12/12, widget suite 829/830 with the pre-existing
frontend-lifecycle failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
The reference wraps these two widgets in a middle .theme-box layer
(widget-container > .theme-box > title + card), which is what supplies the
20px bottom margin. Our outer .jio-widget-box-wrap is only a transparent
layout container, so dropping .theme-box left the quote card flush against
the following module.
- search/quote: emit the reference .theme-box middle layer; the card itself
stays margin-free like the reference, so the gap is exactly 20px
- keep the generic .widget-content branch for user_card/author_card and the
other reference widgets untouched (the string is contract-tested)
- contract test now pins the .theme-box layer and asserts the quote card
carries no margin class of its own
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Fixes two issues:
1. **Balance recharge disabled on fresh install**: The `pay_balance_s`
setting has `default: true` in SchemaPayment but was treated as
disabled when the key was absent from the database. This meant users
had to toggle it off and back on to activate the feature.
Fixed by using `!isset() || !empty()` pattern to honor the schema
default when the key is missing.
2. **PHP Warning in withdrawal settings**: `get_withdrawal_settings()`
passed `$s['affiliate_withdrawal_reset_time']` directly to
`preg_match()` without extracting it first, causing "Undefined
array key" warnings when the option was unset.
Fixed by extracting to `$reset_time_raw` before the regex check.
Changed:
- `PaymentRuntime::get_payment_recharge_settings()`: enabled defaults to true
- `PaymentRuntime::get_payment_gateway_settings()`: balance_enabled defaults to true
- `PaymentRuntime::is_gateway_enabled('balance')`: defaults to true
- `PaymentRuntime::get_withdrawal_settings()`: no longer warns on missing key
Added test script `tools/test-payment-runtime-defaults.php` to verify
the fix works with empty settings (fresh install).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
A fresh install leaves `permalink_structure` empty, so WordPress snapshots
$wp_rewrite with no post/page permalink rules and every single post resolves
to 404 until someone re-saves Settings -> Permalinks.
The existing mitigation fired too late to help: routing.php wrote the option
and flushed from `init`, but WP_Rewrite was already constructed with the empty
structure, and core defers pre-`wp_loaded` flushes to a `wp_loaded` callback
that regenerates from that stale snapshot. The rewrite_rules option ended up
non-empty but broken, so the 404 persisted on every request.
New \Jio\Core\RewriteBootstrap owns this: on `after_setup_theme` (still inside
wp-settings.php, before wp-blog-header.php parses the query) it calls
$wp_rewrite->set_permalink_structure() — the same API wp-admin/options-permalink.php
uses — which persists the option *and* refreshes the in-memory snapshot, so both
the current request and the rules written for the next one are correct. A
structure an administrator chose is never overridden.
Also consolidates the two duplicated flush sites that lived inline in
functions.php (`after_switch_theme` closure and the `jio_flushed_version`
block, whose `delete_option('rewrite_rules')` forced a needless regenerate),
and drops the now-redundant permalink write from routing.php. The version
stamp moves to `wp_loaded` priority 20 so it sees the complete rule set
instead of the pre-`add_rewrite_rule()` snapshot.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
The mechanical rename turned AKSRT-theme into Jio-theme, but the
directory itself is Jio. Fix the three places that name the folder
(README install steps and tree, wp-cli-visual-fixtures usage comment,
DocsPage docs deep-link) and rebuild the SPA bundle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
user_medal_args was declared as type=json without sanitize_type, so
sanitize_field_value() fell through to sanitize_text_field((string) $value)
and stored the literal string "Array". validate() has no json shape check,
so the save reported success.
Consequence: parse_medal_catalog() json_decoded "Array" into an empty
catalog, the dictionary came back empty, and user_context() dropped every
owned badge — the front end showed "获取徽章" and the grant modal listed
nothing, hiding both the newly added and the pre-existing badges.
Add the missing sanitize_type to match its sibling level/integral fields,
and guard sanitize_field_value() so structured container types can never
reach the scalar fallback again. Mirrors the declaration in the Vue schema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
find_missing_admin_chunks() only parsed index.js, but a lazily
imported page chunk pulls in its own deps (shared-api, PageHeader,
sortable-options…). A deploy that drops one of those still leaves the
entry internally consistent, so the existing notice never fires and the
failure only shows up when an admin opens that menu — every schemaPage
keeps working, which makes the breakage look arbitrary.
The same scan never looked at CSS at all: 18 stylesheets are reachable
from the graph and none of them were checked, so a missing page
stylesheet ships an unstyled screen with no warning.
Walk the module graph transitively from index.js, queueing each
discovered dependency, and match .js and .css specifiers. Deleting
PageHeader now also reports every page that depends on it.
This is a diagnostic hardening only — it makes an incomplete deploy loud
instead of guessing at the cause of a broken page.
AksImageSelectField pinned every preview to a 60x60 box with
object-fit:cover, but these are wide layout strips: header layouts are
597x57 (10.47:1), the footer template is 800x190 (4.21:1) and the slide
align set is 200x101. A 10:1 strip inside a square crop keeps ~6% of its
width, so the layout it is supposed to illustrate is unrecognisable.
Constrain the width instead and let the height follow, which is what the
reference theme's CSF image_select does (max-width:100%; height:auto).
Each field now declares the width that suits its images, and an option
may still override it: header 200, footer 240, slide align 160, mobile
header 120.
Only presentation changed. No image files and no stored values moved,
so existing settings are unaffected.
generate-fa5-icons.php read fa-{brands,regular,solid}.svg from
webfonts/, but only woff/woff2 ship with the theme, so the generator
died on file_get_contents() and the committed fa5-icons.php could not
be regenerated from what the theme actually distributes.
Decode the style split from each shipped woff's cmap table instead.
The split has to come from the font: FA5 assigns different drawings to
the same codepoint per style, so fas/far/fab membership is unanswerable
from the CSS alone. Output is byte-identical to the committed catalog
(1001/151/456), so the picker is unaffected.
all.min.css also declared eot / eot?#iefix / ttf / svg fallbacks for all
three families while webfonts/ holds only woff2 + woff, so a browser
that stops at woff requested six files that do not exist. Add
tools/strip-fa5-legacy-fonts.mjs to keep the src: list to what ships;
it is idempotent and touches nothing but the @font-face blocks.
assets/admin/public/images/header-layout duplicated
assets/images/header-layout byte for byte (all five PNGs, matching
md5). The directory only existed because assets/admin/public is Vite's
default publicDir, so every build copied it into build/images.
Nothing referenced it: SchemaPageDisplay builds its preview URLs from
assets/images/header-layout via self::asset(), and a repo-wide search
finds no reference to public/images anywhere. Remove the source and let
build/images stop being regenerated.
The bulk row gained the shared aksrt-native-bulk class in 1273a013, so the
exact class="aksrt-inv-bulk" string match no longer held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Overview page: realtime today vs. same time yesterday, 6 KPI cards with
period-over-period deltas, trend (paid / net / refund curves + order count
line), category curves, payment method donut, top 10 paid content,
conversion / close / refund rates, todos and shortcuts
- Date presets, custom range and category filter via
GET aksrt/v1/admin/pay-overview (manage_options + wp_rest nonce)
- Metrics: paid amount by paid date including later-refunded orders, refunds
by refund date, net = paid - refund; CNY only, points and recharge excluded,
foreign-currency orders listed separately; integer cents internally
- Daily records cached per month in transients (today/yesterday live),
invalidated on order status change and on purge
- ECharts 6.1.0 subset bundled to assets/js/runtime/libs/echarts-pay.min.js
(npm run build:pay-charts), loaded only on the overview page
- Split payment-attempt methods out of OrderRepositoryCore into
OrderRepositoryAttempts; remove dead get_stats(), cash_decimal(),
Order::get_stats() and the old .aksrt-native-links styles
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Split PayWithdrawals into shell + PayWithdrawalsList flat sibling trait
- Add process/policy callout, open/paid/fee stats, status views with counts
- Filter by source and payout method; reset keeps the status view
- Show net payout with gross/fee breakdown, copyable payee, note column
- Replace inline select form with per-row actions and a confirm dialog;
reject requires a reason; double-submit guard; retry reuses the
idempotency key; redirect back preserving filters and paging
- Withdrawal: add get_status_counts() and method filter; get_stats() now
returns open_gross / paid_net / paid_fee
- Remove unused REST routes (/payment/withdrawals, /transition),
admin_withdrawals(), update_status(), has_open_withdrawal(),
count_open(), resolve_gross_amount()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Always render the checkbox column and bulk bar; disable with a reason
instead of hiding when settlement is off or the page has no pending rows
- Replace the batch-settle button with a bulk action select (settle / delete)
plus apply, matching the charge cards and invite codes pages
- Add per-row delete for pending records and Revenue::delete_pending(),
guarded by status = 'pending' so settled ledger-backed rows are never removed
- Report done / failed counts for bulk settle and delete
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add shared .aksrt-native-bulk row that locks select and button to the
same height, font size and line height
- Apply it to the charge cards and invite codes bulk action rows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Move "新建优惠码" / "批量生成" into header-launched <dialog>s
- Extract forms into PayCouponsForms trait with shared rule fields,
hints, validation and currency/percent unit switching
- Generated codes card gets a copy-all button; list codes copy on click
- Drop old inline forms, render_generated_dump and the unread
aksrt_coupon_generated redirect arg
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rebuild aksrt_pay_products as list + in-page quick settings dialog
(pay type, limit, cash/points prices, sales float, coupon, order expiry, copy)
- Query by _aksrt_pay_type with real pagination; drop order-summary fallback
and the duplicate "Meta 销量" column
- Centralize pay meta writes in DomainModel::persist_pay_meta(); stop writing
unread _aksrt_pay_modo / _aksrt_pay_limit keys
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep ticket info at its natural height and let the internal notes card fill
the remaining sidebar height, so only the notes list scrolls.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Share one --aksrt-ticket-pane-height between the conversation panel and
the sidebar cap so both columns line up, and drop the sidebar sticky
offset that no longer matches the pane height.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Put the reply composer at the bottom of the conversation card so the
timeline and composer form one viewport-height panel; the send button is
visible on load and Ctrl/Cmd+Enter submits. Internal notes now open in a
dialog instead of expanding inline, so the sidebar no longer grows.
Remove the shared ticket_composer helper and the inline note expansion CSS;
both dialogs share ticket_dialog_head.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move "代客提交工单" back into a native <dialog> on the ticket list while
the ticket workspace stays a full page. Closing asks before discarding
input, ignores backdrop clicks, resets the form and returns focus; an
uncertain create result keeps the form locked behind "重新加载".
Remove the ?view=new create page route and its styles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ticket handling on users.php?page=aksrt_tickets no longer uses dialogs.
The list, the workspace (?id=N) and the assisted create form (?view=new)
are server-rendered page views that carry the list filters and page
for going back. Writes still go through the ticket REST routes; on
success the page reloads with a native notice, and unsent drafts in
other forms survive the reload via sessionStorage. Uncertain or
conflicting writes lock every form and only offer a reload.
Remove dead code: UsersMenusTicketsDialogs, the aksrt_ticket_view AJAX
read and its nonce, dialog CSS/JS (backdrop, scroll lock, focus return,
read races) and the dialog runtime test. Fix the create link carrying
an unclamped page number on out-of-range list pages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Replace the type/method tab rows with status views backed by one GROUP BY
count, move filters into a single toolbar card, show gateway names, keep
list context across detail and actions, and split the detail page into a
summary/timeline column and an actions/buyer/note sidebar. Confirmations
move to admin-orders.js; PayOrders splits into flat List/Detail traits.
Remove dead code: unused Order AJAX endpoints and init(), get_orders_count(),
shipping flash codes, and the legacy raw-note fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>