- 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>
- 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>
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.
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 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>
widgets/frontend.css loads after main.min.css and its global
.zib-widget{padding:15px} overrode .padding-6.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
Global input[type=radio] rules in main.css out-specified the ticket
picker's hide rule, pulling the radio back into flow before the pill.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- aksrt_ticket gains urge_count / last_urge_time (schema 3.1.0)
- cooldown moves from transient to an atomic conditional UPDATE
- urge inserts a system reply rendered as a centered notice (not quotable)
- admin list badge + info card row for urge count / last time
- new ticket_urge_admin mail event via aksrt_ticket_urged
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Detail modal: fixed-height dialog, thread is the only (thin) scroll area
- Collapsible pinned summary bar (collapsed by default, state kept across re-render)
- Composer: toolbar (image / urge / close) above input, pending image tray with remove
- Paste screenshots to upload; create form uses the same button + tray
- Urge/close now POST (routes are CREATABLE); urge cooldown countdown via urge_wait
- Notify admins on urge when ticket is unassigned
- Split composer/form styles into tickets-compose.css; drop dead uploader code
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
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>
The blue download anchor rendered square while the adjacent yellow close
button stayed round. Both carry .but.cir, so the asymmetry had to come
from a rule matching only one of them.
credits.css:334 scopes .uc-content .but.jb-blue to a flat #3b82f6 skin
with border-radius:4px — that rule exists to stop the same class
flattening the top-nav publish chip and the site-wide login/submit/badge
buttons, so it stays. But the poster modal is emitted inside .uc-content,
and at (0,3,0) loading after main.css it out-ranks .poster-share
.but.cir's (0,2,0) on both counts, overriding the 5em pill with 4px on
the blue button only. jb-yellow is not in that selector, which is exactly
why its neighbour survived.
Restate the pill at matching weight, scoped to this modal. Padding comes
along because .uc-content .but.jb-blue also sets 0.45em 1em, which would
otherwise re-inflate the 36px circle. The 36x36 box and drop shadow stay
with .poster-share .but.cir in main.css.
Add a contract test for the radius restatement and the sizing it relies
on; verified it fails when the selector is renamed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The field rendered in two places on /user/revenue/: the 我的佣金 card's
meta line and the 累计佣金 row, both gated on a non-zero balance. Remove
both, along with the now-unused $debt_amount local and the dead
.uc-commission-card__debt rule.
Source trace, since the label is only the surface: CommissionLedger::
overview() reads debt_minor from aksrt_commission_debt and returns it as
'debt'; record_debt() books the shortfall when a refund cannot be clawed
back in full, and repay_debt_from_pool() later clears it out of the
commission pool. That mechanism is untouched — arrears still accrue and
still get repaid, the UI just no longer surfaces them. The reference
theme's rebate card has no equivalent concept, so this is not a parity
target either.
Keep 'debt' in the template's overview() fallback array: it mirrors the
real return shape and the repayment path still depends on the value.
Add a contract test asserting the label, its class and the local are gone
from the display layer while overview() keeps returning the key; verified
it fails if the label is reintroduced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two regressions from the previous commit made /user/revenue/ unusable.
The rewrite dropped data-aksrt-affiliate-poster-modal from the modal shell.
That single attribute was both the JS lookup key and the CSS hook, so
initAffiliatePoster() bailed at `if (!button || !modal) return` — which is
why the 推广海报 button never got un-hidden — and no CSS could target the
shell. Restore it.
With the shell back but still visible, main.css's .flex{display:flex} loads
after bootstrap and matches .modal{display:none} on specificity, so the
resting modal rendered as a full-viewport flex box. Combined with .fade's
opacity:0 that is an invisible overlay swallowing every click on the page.
Pin the resting state to display:none and the open state to flex, the same
way credits.css already does for the charge and points-pay modals — that
file documents this exact trap.
Add a contract test asserting the markup, JS selector and CSS hook share
one attribute and that the resting state is explicitly hidden.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 推广海报 modal on the revenue page used a bespoke .uc-modal card —
white panel, 12px radius, heavy shadow, title bar, corner × button and a
full-width 保存海报 button — plus a light-gray plate with its own
border-radius around the poster canvas. None of that exists in the
reference theme, so the popup read as bordered and off-spec.
Rebuild the modal on the reference poster-share structure: a borderless
360px .modal-dialog.poster-share centred by .modal.flex.jc, a
.loading-mask overlay while the canvas renders, and the download/close
buttons moved onto a .abs-center.hover-show-con layer that only fades in
on hover. initAffiliatePoster() now drives Bootstrap 3 modal semantics
(.in toggle, .modal-backdrop, body.modal-open, ESC and backdrop-click to
close), mirroring the existing openChargeModal/closeChargeModal pair, and
animates .modal-content from the 220px placeholder to the poster's real
height before clearing the inline styles.
No CSS added — every rule the reference modal needs was already ported.
Drop the now-dead .uc-affiliate-poster rules; the shared .uc-modal shell
and openModal/closeModal stay for the withdraw and transfer modals.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user-center sidebar rendered Font Awesome glyphs while the reference theme
paints multi-tone inline SVGs, and several glyphs were simply wrong for their
slot: fa-chart-line served both 我的等级 and 推广收益, fa-money-bill served both
余额 and 积分, and fa-pen stood in for 创作收益.
The theme already ships every sprite key the reference sidebar needs in
assets/js/runtime/svg-icon.js, and main.min.js injects the <symbol id="icon-*">
set on every frontend page, so no new icon system was needed. The two grids now
use the reference theme's icon-but-box / user-icon-but-box shell (four per row,
em16 icon, 12px muted label) in place of AKSRT's tinted 38px pill, and every
remaining sidebar glyph and arrow resolves through aksrt_icon_markup():
我的服务 我的等级 trend-color, 官方认证 user-auth, 我的订单 order-color
功能设置 消息通知 msg-color, 个人资料 user-color, 账户安全 security-color
资产卡 余额 money-color-2, 积分 points-color
中心卡 创作收益 merchant-color, 推广收益 money-color
其它 VIP 入口 vip_1, 抽屉触发器 menu, 箭头统一 right
Dead code went with the swap: the whole .uc-service-grid block including the
unused --cart and --revenue tones, the count badge (no caller ever set count),
and the tone key. The two card selectors that targeted <i> now also cover <svg>
so the icon sizes survive the element change, and the VIP arrow keeps
uc-sidebar-vip__arrow for its hover animation. level/profile/settings gained
their missing tab keys, which completes the icon-but-* class names and lets the
existing .is-active sync cover all six entries.
Verification: node --test across widget-system, navigation, admin,
asset-ledger, dev, front-submit and fixtures (620 tests), plus php -l on the
five changed PHP files. Contract-locked in
tests/widget-system/user-center-sidebar-icons-contract.test.cjs.
Wrap the provider glyph in the reference theme's .social-login-item circular {type} wrapper on the /user/settings/ bindings list so each row paints the provider brand colour instead of a muted ink glyph. Aggregated clogin_* providers strip the prefix for the colour class while data-provider keeps the full key for the bind/unbind flow. Drops the now-unreferenced .binding-item__icon rules from the user-center settings and profile lists stylesheets.
The "other login methods" block drifted from zibll on three axes. Gitee was
mapped to fa-code-branch because FA5 Free ships no Gitee brand glyph, so the
button showed a generic branch icon; Google showed the monochrome FA glyph
instead of the reference's multi-colour mark. The modal also printed an
implicit agreement line under the social buttons, which the reference guest
card does not have. And the top-nav user popover built its own
.social-login-divider / .oauth-row / .oauth-btn markup and then wrapped that
in a second separator and .social_loginbar, so the block rendered nested and
unlike the reference.
aksrt_brand_icon() now routes zibsvg-/customsvg- values through the shared
icon renderer, and the brand map resolves gitee to zibsvg-gitee and google to
zibsvg-google-color. Both sprite keys already exist in the runtime symbol
sheet, which the loader pulls in on every frontend page. Because
get_auth_providers_data() and binding_row() both go through
aksrt_brand_icon(), one map change fixes the icon on the modal, the user card,
the top-nav popover, the user-center binding list and the profile social
links.
render_social_buttons() is rewritten to the reference shape: an optional
.social-separator, a .social_loginbar, and one .social-login-item <type>
toggle-radius anchor per enabled provider with rel="nofollow" and a
"<label>登录" title. oauth_button_lg switches to the button-lg variant with a
label, and the WeChat entry carries qrcode-signin so auth.js opens the QR
panel instead of navigating, matching the reference. The navbar and the link
apply modal now append the block verbatim instead of re-wrapping it, and the
user-card loop adopts the same item attributes. The retired
.social-login-divider / .oauth-row / .oauth-btn rules are dropped from
base-1.css, and the binding-list icon rule gains an svg selector so the
sprite icons keep the 22px the FA glyphs had.
Verification: node --test across widget-system, navigation, admin,
asset-ledger, dev, front-submit and visual (613 tests), plus php -l on every
changed PHP file. Extended the auth source contract with the agreement-line,
brand-icon and popover-structure cases, and pinned the user-card guest
markup.
The user-center route rendered a 4px top-nav publish chip while every other
route kept the 50px pill. assets/css/routes/user-center/credits.css restated
border-radius on bare legacy colour modifiers (.but.jb-blue, .but.b-blue-2,
.but.c-blue-2.hollow, .but.c-green.hollow). Those rules are (0,2,0) and load
after public.css, so they out-rank .but.radius's 50px and flatten the publish
chip, which is newadd-btns but jb-blue radius, down to 4px.
The same leak also repainted every site-wide .but.jb-blue outside the panel
with the flat #3b82f6 skin instead of the legacy gradient, so login, submit,
badge and cashier buttons lost their reference surface on that route too.
Scope the four rules to .uc-content, the shell every credits panel and its
modals render inside (templates/pages/page-profile.php wraps
templates/parts/user-center/panel.php). The panel keeps its own button skins
and the top-nav chips get the reference pill back.
The .btn-block restatement that sat in the same block is byte-identical to
the one in core/buttons.css, which loads earlier on every page, so it could
never win; it is dropped rather than scoped.
Verified: node --test across tests/ (604 passing), plus a stubbed navbar
rendered against each route's real stylesheet set measuring the computed
border-radius of the publish chip and of the panel's own buttons. A new
contract test fails on the previous stylesheet and passes on this one.
The two large hero avatar frames go back to their legacy rounded-square look: the user center profile header keeps 6px and the public user home hero keeps var(--radius). Both declare an explicit radius because the global avatar primitive (img.avatar, 0,1,1) ties them on specificity and only the later route rule wins the cascade.
Everything else stays: the primitive still circles the bare avatar-mini in the ban and report modals, the VIP modal avatar and the post-card meta avatar, the friends page avatar is circular, and the VIP icon still leads the badge row. The contract test now guards the two hero frames as rounded-square so the primitive cannot silently re-circularize them.
Make --radius-avatar the single source of truth for frontend user avatar shape. A new primitive in components-ext.css (img.avatar / avatar-mini / avatar-md / avatar-lg / .avatar-img img) forces a circle on every avatar image, which covers the bare avatar-mini in the ban and report modals, the classless img inside the VIP modal's .avatar-img, and the post-card meta avatar that previously depended on post-card.css being enqueued. The user center header frame, the public user home hero and the friends page avatar switch to the same token because route CSS loads after the public bundle and would otherwise override the primitive.
The VIP icon no longer rides the avatar corner. The user card widget, the after-article author card and the sign-in modal stop opting into aksrt_get_avatar_box()'s corner badge, and the icon renders first in the badge/level row instead, since aksrt_get_user_name already orders name -> VIP -> auth -> medal -> level. The sign-in modal appends it after the display name and keeps the legacy precedence where a ban badge takes the VIP slot. The user card widget name row now shrinks its display name so the extra icon cannot overflow a narrow sidebar.
Verified with the new frontend-avatar-shape-contract test plus post-visual, user-card-runtime, circular-button-icon-margin, user-features and page-display contracts, and php -l on every changed PHP file. The public bundle was rebuilt with tools/build-public-assets.mjs.