- Point 站点 Logo / 暗色模式 Logo schema defaults (PHP + VUE) and the
Settings::logo_url() fallback at assets/img/logo.png.
- Point the footer night image default at the same file.
- Remove logo_dark.png and the now-unreferenced assets/icons/default-logo.svg
along with the private default_asset_url() helper that was its only reader.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
The previous commit put the channel-switching code in the wrong init block.
credits.js initializes two modals side by side — the points recharge dialog and
the withdrawal dialog — and the snippet landed in the recharge one, which has
no [data-jio-withdraw-method] element at all. Both symptoms followed from that:
methodField resolved to null so no change listener was ever bound, and the
initial sync never ran, leaving the note paragraph permanently hidden.
The block now lives inside initWithdraw(), and the stray copy is gone. The
behaviour itself was already correct — switching channels swaps the account
placeholder and shows or hides the channel note — it just never executed.
Verified in a headless browser against the real markup and stylesheet: the
placeholder starts as the first channel's hint and the note is hidden; switching
to the second channel swaps the placeholder and reveals the note as
display:block; switching back hides it again as display:none.
The contract test previously only checked that the code existed, which is why
it missed a placement bug that made the whole block dead. It now asserts
syncChannelHint appears exactly once, sits after initWithdraw(), and does not
run past the next function definition. 873 tests pass.
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.
The previous commit added five direct clients (Douyin, Microsoft, Facebook,
Twitter, Apple) written against each platform's own endpoints. Maintaining
five bespoke OAuth implementations — including Apple's ES256 client secret and
Twitter's PKCE — is maintenance the bundled yurunsoft SDK does not ask for,
and none of them could be verified without production credentials.
Only providers yurunsoft/yurun-oauth-login actually implements stay on the
direct surface: GitHub, Gitee, QQ, Google, Weibo, Weixin, Baidu and Alipay.
Baidu and Alipay move into OAuthProviderBroker's existing client() switch and
normalize() map rather than living in a separate class, so there is no new
layer to reason about.
OAuthProviderClient, its harness and its contract test are deleted outright
(-809 lines). The Apple form_post handling added to handle_callback() is
reverted too — without Apple there is no POST callback to read.
The aggregate-login channel keeps its 16 platforms, matching the reference;
that list describes what the aggregation provider offers, not a local
implementation. Brand icons for those names stay for the same reason.
Verification: 869 widget/navigation tests pass, the PSR-4 audit reports 0
errors, and the catalog constant, PHP schema and Vue schema now list the same
16 aggregate platforms.
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.
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.
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.
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>
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>
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.
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>
- 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>
- 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>
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>
Render the original question and replies as one chat-style timeline,
merge editor and toolbar into a shared composer for replies and notes,
and refresh sidebar cards. Remove superseded renderers and selectors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Edit status, department and assignee directly in ticket information, remove the redundant handling card and its layout rules, and cover editable and closed-ticket rendering.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep the ticket number and status in the sidebar only, remove the unused header description payload and bindings, and update dialog regression coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Move on-behalf ticket creation into a header dialog and load ticket handling on demand. Reuse the admin REST write path, preserve drafts and filters, and cover dialog and server boundaries with regression tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Store readable category snapshots separately from details and evidence, validate report categories, and preserve context in admin displays and moderation actions. Widen report categories to TEXT and bump the schema to 3.2.0 without legacy data conversion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use highlight-only reason choices, show the associated target on one line, and replace inline submission progress with shared toast feedback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mark new regular and chunked uploads as pending and claim persisted article references, including partial saves and large reference sets. Protect associated media from cleanup and add upload lifecycle regression coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Accept the stdClass user data passed by WordPress and derive the user ID only from the hook object. Cover callback registration, nonce and permission guards, and mismatched posted IDs; refresh the identity review dialog contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace inline identity review forms with focused detail and review dialogs, improve list filters and caching, and align evidence permissions and deletion feedback. Add contract and runtime regression coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unify status filters, search, bulk actions and pagination in one invite list card. Remove the standalone filter summary and preserve usable search widths on small screens, with structural regression coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>