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.
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>
Clicking 加载更多 showed no skeleton at all, and the placeholder shape did
not match the reference theme's post cards.
- Class-name mismatch: pagination.js queried .post-ajax-loader while every
PHP producer emits .post_ajax_loader, so showSkeleton() never found a
target and the loader stayed hidden. Accept both spellings.
- Inline display:none on the loader (pagination, comments, widget tab
panes) outranked .is-loading, so even the correct selector could not
reveal it. Drop the inline style; the reference uses a bare
<div class="post_ajax_loader"> and toggles visibility from JS.
- loadNextPage() set indicator.style.display = "flex", which forced the
.posts-item.card skeleton blocks (inline-block layout) into a single
row instead of stacking vertically — the visible symptom. Remove those
inline writes and drive .is-loading from CSS for every surface.
- Scope the hide/show rules on the class itself rather than requiring an
.ajaxpager ancestor: homepage tab panes, archive streams and comment
lists sit at three different depths.
- Rebuild aksrt_render_placeholder() on zib_get_post_placeholder's
structure — list = t1/k1/k2 + two s1, card = t1 item-heading/k2 + two
s1, thumb wrapped in post-graphic + radius8 item-thumbnail, all carrying
ajax-item so the loader is not mistaken for a real card.
- Add mini/mini_thumb modes so ContentRenderer::placeholder() stops
hand-rolling the same markup; normalize its 'lists' arg to 'list'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- detail-tab 累计佣金 row now shows 累计 + 已提现 (was 累计/可提现/已提现)
- rebuild poster canvas to match reference layout (768×1168, top banner,
date overlay, blue title bar, bottom bordered box with QR + link)
- copy-link feedback moves from inline text to a toast (notyf), matching
the reference; drop the now-unused data-affiliate-feedback element
Aligns affiliate commission with the reference theme: commission is credited
to a dedicated COMMISSION asset pool the moment an order is paid, removing the
7-day settlement freeze, the settlement cron, and the settlement_days setting.
- Ledger: add AssetType::COMMISSION (isolated from the recharge balance wallet)
- CommissionLedger: credit-on-record + opportunistic debt repayment; refunds
claw back the full commission from the pool, carrying any shortfall as debt
(aksrt_commission_debt) that future commission pays down first
- Withdrawal: make asset-aware via a source discriminator (commission vs
balance); each pool keeps independent daily limits, pending gate, and
reject-refund. Balance withdrawal (recharge + creator income) is unchanged
- Fix pay_rebate_self_s: honor self-commission at purchase time
- Revenue card: show pool balance + 累计/已提现 (drop 待结算), debt when present
- Schema 2.0.0 -> 2.1.0: add commission_debt, drop freeze columns
Opening the 推广收益 tab was slow because every read path ran settlement
and two COUNT aggregates before the browser could show the link.
- profile-revenue.php: the link input now carries a server-rendered value from
ReferralAttribution::create_link(), so 复制链接 works on first paint instead
of after a REST round trip
- CommissionLedger::overview() no longer calls lazy_settle_due(); the
lazy-settlement hook and its static flag are gone. Settlement runs only on
the existing hourly aksrt_affiliate_settle_due cron, which uses a larger
batch (100 vs 20) and would otherwise re-run up to 20 locked
SELECT..FOR UPDATE + write cycles inside a GET request
- ReferralAttribution::rest_me() drops the lazy_settle_due() call and the
stats() result; stats() itself is deleted — its two COUNTs (one of them
COUNT(DISTINCT source_user_id)) had no consumer left once the 推广人数 row
was removed
- credits.js: reads the link from the already-rendered input and only fetches
/revenue/me for poster config and commission ratio. A failed fetch no longer
blocks copying — it silently skips the poster enhancement
- tests: the lazy-settlement contract is inverted to assert the read path stays
read-only and cron stays wired; new assertions pin the server-rendered value
and the no-COUNT REST payload
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 推广收益 detail card no longer renders the QR code as its own row. The
card becomes a fixed-label info list: 推广链接 [Url][复制链接][推广海报] /
佣金比例 / 累计佣金 / 返佣详解, mirroring the reference theme's 推广中心 card.
- profile-revenue.php: QR row removed; poster button moved into the link row and
gated server-side on the poster switch; copy label now 复制链接; 推广人数 row
dropped; 返佣详解 heading reads the existing pay_rebate_text_details_title;
dead $vip_ratios, aksrt-revenue-page class and data-min-amount removed
- new affiliate-poster-modal.php: poster previews in a modal before download
(reuses .uc-modal + single-render guard), since the QR now only exists inside
the poster
- credits.js: renderAffiliateQr and its call deleted, along with the dead
[data-affiliate-payers] lookup and the inline ch-width hack; initAffiliatePoster
builds the poster into the modal stage and reveals 保存海报
- PaymentRuntime: expose affiliate details_title
- user-center.js: remove never-defined AKSRTRevenueCenterInit guards (2 sites)
- CSS: revenue-earnings.css pruned to live rules plus the new link-row grid and
poster stage styles; routes/user-center/revenue.css pruned to its only
definitions; dead leaves routes/revenue.css and routes/commerce.css deleted and
dequeued from the user-center part list
- tests: referral-qr-contract test 2 rewritten for the QR-free card, link row and
poster modal; post-visual-contract leaf list updated with regression guards
qrcode.min.js stays enqueued — buildPosterCanvas still draws the QR inside the
poster. Net -833 lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The action bar used a locally invented pill-chip style that overrode the
reference theme's 80px icon-over-text columns, and the JS toggled a
class no stylesheet targeted — so likes/favorites showed no selected
state at all.
- restore reference markup: <svg class="icon"> + <text>/<count> and the
actived class, with data-stat-count/data-base-count preserved
- toggle actived plus a transient action-animation so the lovestar
keyframes finally reach the DOM
- drop the CSS overrides and now-dead action-btn/.action-btn--reward
and .post-actions-card rules; main.css already carried the reference
rules the overrides were shadowing
Hovering the remark gave no affordance hint that it copies anything. The
reference theme titles the same button 点击复制{copy name} and omits the
tooltip entirely when no copy name is configured.
Emit data-toggle="tooltip" with that title on both copy buttons, gated on a
non-empty copy name, and mirror it on the paid-card helper so the two
surfaces agree. Bootstrap's tooltip init already runs in auto_fun() on load,
which is where the clipboard lib is required too.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The post editor's download row only exposed address / button text / remark,
leaving copy_key, copy_val and class as hidden inputs with no UI, so the
reference copy area, custom button icon, text and color were unreachable.
The submission popup already emitted those keys, which made the two write
paths diverge from anything the editor could produce.
Expose the full reference field set (地址/备注/复制名称/复制内容/图标/文案/
颜色) as an accordion card with drag reordering, add the one genuinely new
`icon` key through aksrt_sanitize_icon_value(), and teach both frontend
download renderers to consume the fields they previously ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The token relay still stood between the button and the file: every click
issued a short-lived token URL, so downloads never reached the real
source address directly.
- Render direct anchors (target=_blank) to the raw source URL in the
download box and the paid card, with inline extract-code buttons
- Retire the relay: delete Content\Download plus its init wiring, header
query-var branch and SEO noindex branch, and drop the dead
download-request flow from cms-download.js (cashier, scan-pay and
error views stay)
- Keep the dl_token table and the download logs page as read-only
history; unscheduling of the orphaned cleanup cron is preserved
The runtime kept reading aksrt_download_config after the settings page was
removed, so the frontend still issued download links per the stale option
values instead of the built-in defaults.
- Pin endpoint, per-token uses and TTL to built-in constants and drop all
option reads plus the is_enabled() master switch
- Remove the now-unreachable direct-download branches from the download
box template and the paid-card download helper
- Lock the pinned contract in the download contract tests
- Rewrite post_list/filter schema (PHP + JS) with the reference theme's
field set, order, defaults and dependencies; topics fields excluded
(no topics taxonomy), AKSRT private filter_* toggles dropped
- Support dynamic checkbox options in AksField (options as formData key)
so cat_custom_filter.custom_filters lists the custom_filter dimensions
- Map PostDisplay filter settings to the new keys without legacy fallback;
cat_custom_filter now carries lists/dropdown fieldsets and array filters
- Wire the archive chain to reference semantics: cat_custom_filter
overrides per-category filter projects, custom_filter_show gates the
default display, dimension chip groups render on category/tag archives
- Filter archive queries (main query + AJAX endpoint) by _aksrt_cf_{key}
meta with exact-quoted LIKE matching
- Add advanced-filter settings reference contract test