- 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>
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.
The posts/favorites tabs turn the article list into a 2-column grid at
>=992px. The AJAX pagination skeleton .post_ajax_loader is a direct
child of that grid, so with no grid-column it was laid out as a single
half-width card and the whole placeholder stack collapsed into one
column while paging or switching tabs.
Span the loader across every column and, while loading, give it the
same 2-column template the real cards use so placeholders line up with
them. The skeleton shape now follows the resolved render mode instead
of a hardcoded 'list', so card mode no longer renders list blocks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking 加载更多 in the 多栏目文章 widget showed no skeleton at all — the
list simply stayed still until the response arrived, then the new cards
appeared. The homepage stream, archives and comments already had a working
skeleton; the widget's AJAX load goes through a different client and never
found its loader.
Root cause was DOM placement, not visibility: ContentRenderer emitted
.post_ajax_loader as a *sibling* of .widget-ajaxpager, while the widget REST
client resolves it with container.querySelector('.post_ajax_loader'). The
node existed but could never match, so .is-loading was never added and the
skeleton stayed display:none. Inactive tab panes had the same shape, and the
direct-load branch of ajaxpager() emitted no loader at all.
Changes, all following the reference theme's post_ajax() contract in
zibll/js/main.js (beforeSend anchors the loader to the end of the list;
success only hides it, never removes it):
- ContentRenderer: nest the loader inside .widget-ajaxpager for both the
direct-load and inactive-tab branches, and drop the sibling append.
- widgets/frontend.js: anchorLoader() moves the loader to the end of the
list before .ajax-pag on every request; appended cards are inserted before
it; a replace preserves the node instead of wiping it with innerHTML; a
fallback loader is created when the DOM has none; loadItems() now returns
its promise; drop the unreachable is-loading auto-load branch.
- Message centre, user-home panels and the search page switch from spinner /
opacity feedback to the same .posts-item-shaped skeleton, so all AJAX
surfaces share one loading contract.
- Remove the second, never-wired skeleton system (.aksrt-skeleton-* in
core-ui.css) and the dead spinner stylesheets/rules (skeleton.css,
ajax-spinner.css, .load-more__*, --skeleton-base/--skeleton-shine) plus the
unregistered assets/js/waterfall.js.
- Extend the widget, message-centre, user-home and asset-ledger contract
tests to pin the loader inside the container, the class-only visibility
switch, and the absence of the deleted dead code.
Verified: 470 node tests, 136 PHP widget assertions, admin-contract and
widget-system audits all green; rebuilt public assets after the CSS/JS
removals.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The public user homepage cover showed a single bottom-right 粉丝 pill,
which the reference theme does not render there — it shows 人气值 and 点赞
as a translucent pill group pinned to the cover's top-right corner.
- Cover metas: replace 粉丝 with 人气值 (zibsvg-hot) + 点赞 (zibsvg-like),
summed over the user's published posts via aksrt_get_user_posts_meta_sum()
and abbreviated with aksrt_cut_count(); each is zero-suppressed
independently, matching zib_get_author_header_metas()
- Titles use the reference strings 人气值 N / 获得N个点赞 via
data-toggle="tooltip"
- CSS: adopt .single-metabox.cover-meta semantics (one rgba(0,0,0,.3) pill
at right:1em/top:1.6em, 14px, 6px item gaps, 3px icon margins) plus the
reference's <=480px override; drop the per-stat bottom-right pills
- Drop the dead __stats / __stat / __contacts CSS blocks and the unused
$tab_counts hero contract arg
Follower counts remain in the content tab bar (tab_counts['follow']).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove 主页概览/快捷入口/交易服务 private-dashboard sidebar from public home
- Full-width layout; counts live in tab bar (粉丝 = followers)
- Hero: 用户中心/消息 for self, 关注/私信 for others; drop body stats row
- Defaults: no sidebar, posts/favorites/comments/follow tabs