Skip to content

v0.9.6

Choose a tag to compare

@github-actions github-actions released this 20 Sep 22:34
· 84 commits to main since this release

Added

  • A site-wide ratio policy banner
    (#345) — a member on
    ratio watch, or with downloads disabled, now sees it on every
    authenticated page, above the global notices. The profile notice (#334) and
    the System PM both require the member to go looking; a watch runs 14 days and
    carries a 10 GiB download trigger, either of which can pass unseen.

    Not dismissible. A notification is an event you acknowledge once; this is
    derived state that is true until it is not, and a member who dismissed it on
    Monday would get no warning on Thursday as the trigger approached.

    The disabled arm keys on canDownload, not on the policy status. The api
    documents that flag as "an independent download-capability flag, NOT a
    projection of ratio", with ratio policy merely its first writer and future
    abuse suspensions expected to gate it too. Keying on the policy enum would
    leave any such member with dead download buttons and no explanation — the
    exact defect #334 existed to fix. The cause only explains the flag, and
    degrades to a neutral "contact staff" when the ratio domain has nothing
    specific to say.

    The watch arm carries the deadline and the 10 GiB trigger but no numbers:
    a banner is an interrupt, not a report, and /ratio and the profile carry the
    arithmetic.

    Wording is single-sourced with the profile notice in
    components/ratio/ratioPolicyCopy, so one member cannot read two
    explanations of one state on two screens. #334's spec passes unchanged, which
    is the evidence that extraction changed no behaviour.

    It reads the session (stellar-api#659), not GET /profile/me/ratio. That
    route runs an unbounded read over every one of the member's contributions —
    fine behind a profile view, not for something rendered on each page and
    polled.

  • Eligible Contributions on /ratio — contribution coverage is eligible
    bytes over downloaded, and the page showed the percentage with neither
    operand. RatioStats was the only other component that rendered the figure,
    so deleting it (below) would have dropped it from the app entirely.

  • Member Feed settings — view, copy and reset your feed URLs
    (#349) — a new
    Feeds tab in settings surfaces the four RSS feeds stellar-api serves
    (stellar-api#262): new contributions, your own contributions, news and your
    bookmarks. Until now the api served them and nothing in the UI could reach
    them.

    The URLs are credentials and are treated as such. Each carries a bearer
    token, so they render as read-only fields rather than links — a link would
    put the token in browser history — and the token is masked until you choose
    Show tokens. That control is deliberately section-wide rather than
    per-row: the api derives one token per member and maps it into all four
    URLs, so revealing any single row already discloses the other three, and four
    separate toggles would imply a separation that does not exist.

    The contributions feed can be filtered by community, tag, format and
    bitrate, each single-valued and ANDed, with the copied URL reflecting the
    selection. The format and bitrate options are pinned to the contract: they
    are Records over the generated union, so a value added upstream fails the
    typecheck here instead of silently going missing from the list. Bitrates
    render through the existing shared label map, so KbpsV0 reads as
    V0 (VBR). Filters narrow what the api already decided you may see —
    releaseVisibleTo runs first — so no filter can widen access.

    Resetting asks for confirmation, stating that every URL already in use
    stops working, then shows the new URLs straight from the rotate response
    without a second request. A failed reset leaves the displayed URLs untouched
    and reports the api's own message.

    The feed URLs are cached with keepUnusedDataFor: 0, so the token leaves the
    store as soon as the tab is closed rather than lingering for RTK Query's
    default minute.

  • Homepage news items are addressable, and the panel can reach back
    (#348) — every item in
    the announcements panel now carries id="news-<id>", so the links the Member
    Feed's news.xml publishes land on the item rather than the top of the page.

    The panel had to grow to make that true. The feed publishes 50 items;
    the homepage showed five, so 45 of those links pointed at an anchor that
    could not exist. News now comes from the paginated list added in
    stellar-api#670: five rows at rest, a Load more news control, and a
    fragment that widens the request to the feed's whole reach in one go rather
    than paging until it finds something.

    Arriving from a feed link scrolls the item into view, expands it — the
    reader has already seen the body in their reader, so a collapsed headline
    would be a non-sequitur — briefly highlights it, and moves focus to it so
    assistive technology announces the arrival. scrollIntoView moves the
    viewport but never focus, so without that a screen-reader user got no signal
    at all. The scroll honours prefers-reduced-motion, which the stylesheet's
    own reduced-motion block does not cover: it disables transition, not
    scroll-behavior.

    An item the list cannot reach now says so in one muted line, rather than
    leaving the reader at the top of the page unable to tell a stale link from a
    broken one.

Changed

  • Re-vendored the API contract from stellar-api
    (#328) — src/types/openapi.json
    and the generated src/types/api.ts now match the spec the API serves.
    Three merged API pull requests had widened it since the last sync: the Member
    Feed token surface, the four feed documents, and ratio policy state on the
    session.

    Seven new paths, all Member Feed: /feeds/contributions.xml,
    /feeds/mine.xml, /feeds/news.xml, /feeds/bookmarks.xml, the member's own
    /profile/me/feeds and /profile/me/feed-token/rotate, and the staff
    /users/{id}/feed-token/rotate. One new schema, MemberFeeds. Nothing in the
    UI consumes them yet — #349
    is the settings page that will.

    Three paths changed shape in place, and both changes are additive:
    /install/checklist/{id}/dismiss now declares a 429, and the two
    /tools/user-ranks operations carry a new users_edit_reset_feeds
    permission. The rank form renders from the API's live permission catalog
    rather than a list of its own, so the new key appears there with no change
    here.

    No consumer broke: typecheck was clean against the new contract. The
    contract's major.minor did not move (0.9), so ADR-0004 forces no version
    bump.

Fixed

  • The ratio policy notice is mounted again
    (#334) — a member on
    ratio watch, or with downloads disabled, saw nothing on their own profile.
    63d64cf replaced <RatioStats /> with inline stat rows in May and the
    watch and disabled banners went with it; the component and its spec stayed,
    and the spec rendered the component directly, so it kept passing against code
    nothing mounted. #333's
    cause-aware disabled banner has been unreachable since it shipped.

    The notice is now a RatioPolicyNotice at the top of UserProfile's main
    column. Not in the Statistics panel, where the old rows were: that column is
    176px wide and the notice is prose.

    The watch wording now carries what a member needs to act on it, mirroring
    the api's own watch PM: the current and required ratio, the deadline, and —
    new here — the 10 GiB trigger, which can disable downloads tomorrow
    whatever the deadline says. It states no "you must upload X" figure, because
    the required ratio is computed from eligible contribution bytes and so moves
    as you upload; any deficit printed here would overstate it.

    RatioStats.tsx and its spec are deleted, along with a stale
    jest.mock('…/RatioStats') in UserProfile.integration.test.tsx that had
    been mocking a component UserProfile no longer imports. The new spec renders
    UserProfile, never the notice alone — a spec that mounts the notice
    directly would reproduce the bug rather than catch it.

  • A download refreshes the ratio stats it changes — grantAccess and
    reverseGrant invalidated 'Contribution' and 'Download' but never
    'Profile', which getMyRatioStats provides. Both mutations move the
    member's consumed, and ADR-0044 puts OK → WATCH after a download and only
    after a download, so the surface most likely to need fresh policy state was
    reading the pre-download cache.

    One limit, deliberately left: stellar-api evaluates the policy in the
    background after the grant responds
    , so the refetch can still beat the
    transition that sets WATCH. The ratio numbers are now always correct; the
    status flip may lag by one interaction and lands on the next 'Profile'
    invalidation or page load. Closing that is folded into the site-wide ratio
    banner issue, where the same freshness question has to be answered for both
    surfaces at once.

  • The session refreshes after a download, and on a timer (#345) — the
    download mutations now invalidate 'Auth' alongside 'Profile', since the
    session carries the same policy state. That closes only the member's own
    actions: the daily sweep and a staff override move the state with nothing to
    invalidate on, and the api evaluates the policy after the grant responds,
    so invalidation can lose the race. PrivateLayout therefore polls the session
    every 15 minutes — nothing calls setupListeners, so refetchOnFocus is
    unavailable without a global change and a surface needing fresh state must
    poll for itself.

    Measured rather than assumed: each poll hands selectCurrentUser consumers a
    new object reference, so they re-render once per interval, but the content is
    identical so nothing visibly changes. At that cadence it is not worth a
    deep-equality guard in setCredentials, which would alter every login path to
    save four renders an hour. A test pins both halves so a shorter interval has
    to confront the cost knowingly.