Skip to content

Releases

v0.10.2

Choose a tag to compare

@github-actions github-actions released this 02 Oct 20:26

Added

  • A member's profile shows "Invited by" to viewers holding invites_manage (#411, stellar-api#849). It links to the inviter's profile, or says Nobody for a member no one invited. The api withholds the field from anyone else, so the row is hidden for them.
  • The registration log shows each account's inviter and flags a shared IP (#412, stellar-api#850).
    • Each cell shows the new account's value with its inviter's beneath, muted: name, email, rank, registration date and IP.
    • Each IP carries how many accounts hold it now.
    • A row whose account and inviter share an IP is highlighted and marked Same IP.
    • The column is now Current IP, since both addresses are where each account is now, not where it registered from.
  • The invite form shows its staff note only to holders of invites_note (#413, stellar-api#851).
    • Its label now says the note is added to the invitee's moderation notes when they register.
    • The api ignores a note from anyone else, so the field is hidden from them and the form sends none.
    • The note now takes up to 1000 characters, the api's limit, where the input stopped at 255.
  • "Can't find your community?" now opens a pre-filled Staff PM asking staff to create one (#100).
    • The link is on the contribute form and, new, under the list on the Communities page. It read "Submit a request." and went to the release request list, which can't create a community.
    • It opens /inbox/staff/new?template=community-request. The subject starts with a fixed [Create a Community Request] tag, and the member types only their line after it.
    • The message greets the site's staff by name (from GET /rules/tree, or "Hey there staff," when that can't be read) and asks for the community's name, what it would hold, and why the member would lead it. It is signed with their username.
    • Staff create the community in Community Manager with the member as leader. A request flow that does this on approval is stellar-api#890.
  • ?notheme=1 turns member themes off for the tab, so a stylesheet can't lock a member out (#449).
    • Load any page with it, such as /?notheme=1, and no Personal, Registry or registry theme renders in that tab until it closes. The member can then open Settings (Edit) and switch the theme away. A banner says why the page looks plain, and its Turn themes back on button restores the theme. No theme is loaded while the banner shows, so a theme can't hide it.
    • It's for a sheet whose CSS hides the control needed to switch away from it. Staff can paste the link into a reply.
    • The cold-load pre-apply script honours it, so the theme doesn't flash in first. The chosen theme is kept, so turning themes back on, or another tab, still finds it.
  • Members can write, edit and delete their own stylesheets (#450, part of #108).
    • My stylesheets (/stylesheets, linked from Settings' Site Stylesheet section) lists your sheets with the space they use: "3 of 5 stylesheet spaces used", or "(no limit)". At the cap, or for a class without spaces, New stylesheet is disabled and says why.
    • The editor takes a name and the CSS, up to 100,000 characters, with a live count. A .css file can be read into the field; nothing is uploaded.
    • When the api refuses a sheet, every violation it found is listed under the field.
    • Saving an edit says it updates the sheet for everyone who adopted it. Deleting frees a space, and the confirmation says adopters keep the sheet until they switch away.
    • Adopting someone's sheet from its page is the next slice (#451).
  • Every stylesheet has a page a member can share, read and adopt (#451; with #449 and #450 this completes #108).
    • /stylesheets/{id} shows the sheet's name, its CSS read-only, a link to its author's profile, and Adopt. Its author also sees Edit. The page's address is the link to share; there's no gallery.
    • Adopting says "Adopted — your site now uses {name}", and the site switches to it straight away. The sheet already in use shows In use instead.
    • A missing or deleted sheet says "This stylesheet isn't available".
    • On My stylesheets, a sheet's name now opens its page, with Edit beside it.
    • In Settings, the Registry option says "None adopted yet — open a stylesheet's page to adopt it", or links to the adopted sheet's page.

Changed

  • On a community's page, only its leader and staff see the curator controls (#457, stellar-api#895). The api now refuses a curator who isn't the leader when they add or remove another curator, so their Make Curator and Demote buttons are gone.
    • A curator who isn't the leader gets a Step down button on their own row instead. It asks first, because it ends their member management at once, and it says whether it worked.
    • The leader's row has no Demote for anyone, because the api refuses removing the leader as a curator. A leader is reassigned or cleared in Community Manager.
    • Member Add and Remove are unchanged: curators and staff still see them.

Fixed

  • A community's member and curator controls say when a write fails (#464, part of #456). Adding or removing a member, and making or demoting a curator, ignored the api's answer. Each now shows the api's message, such as that a curator has to lose that role before being removed as a member, and a refused add keeps the typed user ID. With this, every write in the ui reports its failure, and mutations:check has nothing left in its baseline.

  • The staff tools say when a write fails (#463, part of #456). Seven writes ignored the api's answer. Each now shows the api's message:

    • Global Notices: a refused notice cleared the form as if it had been sent, and now keeps it; a failed delete did nothing visible;
    • Tag Aliases: a refused edit closed the row as if it had saved, and now stays open; a failed delete did nothing visible;
    • the deletes on Album of the Month and Do Not Contribute, and removing an artist from Vanity House.
  • Messages, notifications and logout say when a write fails (#462, part of #456). Nine writes ignored the api's answer. Each now shows the api's message:

    • the inbox: a failed bulk action cleared the selection as if it had worked, and now keeps it; a single delete did nothing visible;
    • a conversation: sticky, mark unread and delete. A failed delete went back to the inbox as if it had worked, and now stays;
    • the notification corner: mark all read and dismiss; and dismissing a global notice.
    • Logout signs out only once the server has. A failed logout used to clear the session here and go to the login page while the session cookie still worked, so a reload signed back in. It now keeps the session and says it couldn't sign out.
  • The forum and comments say when a write fails (#461, part of #456). Ten writes ignored or swallowed the api's answer. Each now shows the api's message, and an edit or comment keeps what was typed:

    • on a thread: lock, sticky, subscribe, catch up, poll vote, and moving it to the Trash;
    • on a post: a failed edit closed the editor as if it had saved, and a failed delete did nothing visible;
    • on comments: a refused comment kept its text but said nothing, and a failed delete did nothing visible.
  • The admin panels say when a save or delete fails (#460, part of #456). Twelve writes ignored the api's answer. A failed create cleared its form and a failed edit closed its row as if they had worked; a failed delete or dismiss did nothing visible. Each now shows the api's message, and a form keeps what was typed:

    • the forum category and forum managers: create, edit and delete;
    • the News Manager: post and delete, for announcements and blog entries;
    • the User Rank Manager's delete, and the ModBar's setup-checklist dismiss.
  • Community Manager's edit row stays open when a save fails, and says why (#456). The save ignored the api's answer and closed the row either way, so a refused save looked like it had worked, with nothing saved. It now shows the api's message and keeps what was typed, including the new refusal to leave an invite-only or closed community without a leader (stellar-api#892).

  • An emptied donor rank colour or badge now clears (#437). Saving an edited donor rank sent each as value || undefined, and the api leaves an undefined field alone, so the save reported success and kept the old value. An edit now sends both as staff left them, and '' clears. Creating a rank still omits an empty field, so the api's defaults apply (a new rank's badge is ♥).

  • The stylesheet manager's user-count note now gives the real reasons a count can differ from what members see (#203). It blamed an external stylesheet URL on a registry row, which can no longer happen. It now says the count is members whose saved selection is the stylesheet, disabled accounts included; that a member's Personal or Registry stylesheet shows instead; and that a stylesheet with no CSS URL adds no styling.

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 02 Oct 03:15

Added

  • A member's profile links to their invite tree for viewers holding invites_manage (#423).
    • The link opens /user/{id}/invite-tree, served under the same permission by GET /users/{id}/invite-tree. Before, nothing linked to it.
    • It isn't shown on your own profile; your tree is on /invite.
    • It's hidden when the member is known to have invited nobody, as in the legacy implementation.
    • When the count is unknown (the member hides ratio stats from a viewer who isn't staff), the link still shows, so the permission never loses access to a tree that exists.
  • Upload an image for your avatar, donor icon or second avatar from settings (#275).
    • Each of the three fields takes an upload, through the control described under Changed (#434). An upload fills the field with the stored image's /api/asset/… address, and the form's own save stores it.
    • Each upload names the field it will replace, so a member at their rank's limit can still upload the replacement (stellar-api#871).
    • A rank that can't upload (assetLimit 0) gets a note instead. The two donor fields take an upload only when the perk is unlocked.
    • The type is checked before sending. An oversize image says so, and "limit reached" says how to free a slot.
    • This re-vendors the contract for userRank.assetLimit on the session (stellar-api#716) and the upload's field.
  • Staff can delete a release that has no contributions (#429). A [Delete release] action in the release page's links shows to communities_manage once the release's contributions have loaded and there are none, which is the only release DELETE /communities/{id}/releases/{id} accepts (stellar-api#793). It asks for confirmation, and returns to the community on success. A refusal shows the api's message.

Fixed

  • The promotion editor follows the api's ladder rule (#383).
    • "Promotes to" is now fixed: it shows the next primary rank up, the only target the api accepts, instead of a select listing every rank.
    • The section is hidden on secondary ranks and on the top rung, which can hold no valid rule.
    • Rules are matched by their rank pair rather than by the first one leaving the rank.
    • Every other rule leaving the rank is listed as out of date, with a Delete button.
    • A refused save now shows the api's own message.
  • A rank save that takes promotion rules off the ladder now says so (#383).
    • The api reports those rules in staleRules (stellar-api#718).
    • The page stays open and links each rule's rank to its promotion section.
    • This re-vendors the contract for that field.
  • Staff ranks are no longer offered as a promotion target (#425).
    • The editor offered "Promotes to Staff" on Stellarige, though the api refuses that rule (stellar-api#866).
    • The next rung is now the next rank with autoManaged set, so Stellarige is the top rung.
    • The section is hidden on a staff rank, unless old rules leave it, which it lists for deletion with the reason.
    • The ui reads the api's autoManaged and never copies the level threshold.
  • A rank save that moves the rank on or off the auto-managed ladder now says so (#425).
    • The page stays open and names what changed for members holding the rank as their primary rank: auto-promotion and demotion, the inactivity sweep and the invite handout.
    • The same notice still lists any promotion rules the save stranded.
  • An emptied donor field now clears (#432). The donor rewards and forum-title saves sent each field as value || undefined, and the api leaves an undefined field alone. So emptying any reward field, or the title prefix or suffix, reported success and kept the old value. Each field is now sent as the member left it. This is also what lets clearing the donor icon or second avatar free its upload slot (stellar-api#871).

Changed

  • The vendored contract drops the profile's inviteTree and its InviteNode schema, and gains UserRank.autoManaged (#424).
    • stellar-api#856 removed inviteTree, which the ui never read. The ui's invite tree reads GET /users/{id}/invite-tree, which is unchanged.
    • The unused InviteNode type export is gone from types/index.ts.
    • autoManaged (stellar-api#866) arrives with this re-vendor; ui#425 puts it to use.
  • The three image fields are upload-only (#434). Avatar, donor icon and second avatar each show a small preview of the current image, a [Browse] button that opens a file picker and names the chosen file, and [Remove], which empties the field so the save clears it (and releases the upload, stellar-api#871).
    • There is no address box any more, so a new https:// address can't be entered. A saved one still previews through the api's resolved *Src, never loading the remote host, and can be removed or replaced.
    • A rank with assetLimit 0 gets no [Browse] but can still remove. A locked donor field shows its preview and lock note only.
  • The rank editor sets every rank field the api accepts (#342, absorbing #376).
    • New fields: the author stylesheet limit, the image upload limit (assetLimit), and the rank's colour and badge, with a preview of the name in them. An emptied colour or badge clears it.
    • Every limit that can be unlimited reads the same way: personal collages, author stylesheets, image uploads and notification filters each have a number, where 0 is none, and an Unlimited checkbox, which sends null. Before, the collage limit read 0 as unlimited, beside fields where 0 meant none. The api changed its meaning in stellar-api#881.
    • A new rank starts every limit at 0.
    • The rank list shows each rank in its colour with its badge, and its collage limit as ∞, none or the cap.
    • Creating a personal collage reads the session's null as unlimited and 0 as none, which says the rank can't create one.
    • A refused save shows the api's reason, such as the entry rank keeping level 100 (stellar-api#882), instead of a generic failure.
    • This re-vendors the contract for stellar-api#881 (both limits nullable on the session and on ranks) and #882 (the entry rank's 409).

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 03:24

Pairs with stellar-api 0.10.0, whose contract this re-vendors. Deploy after the
api.

Added

  • A member's ratio watch shows on their profile (#417). It needs
    stellar-api #658, which sends ratioWatch to every viewer and ratioPolicy
    to ratio_policy_manage.
    • Any viewer: an active watch, as present facts: when it ends, how far
      short of the required ratio the member currently is, and what they have
      consumed since it began.
    • ratio_policy_manage: the status instead, including a download
      disable and its cause, with a link into the ratio policy tool already on
      that member (/staff/tools/ratio-policy?user=<id>).
    • An override in that tool now refreshes any profile already loaded.
  • A rank holding only an invite permission can see and manage invites on a
    profile
    (#414). It needs stellar-api #655, which sends inviteCount and
    canInvite to a viewer holding invites_manage or invites_edit.
    • invites_edit: the Invites panel mounts on its own, without the rest
      of Staff Actions.
    • invites_manage: the balance shows read-only in the sidebar.
    • Staff: unchanged; the Invites panel stays inside Staff Actions.
    • Older api: with neither field sent, nothing mounts.

Changed

  • The vendored api contract is stellar-api 0.10.0's. No operation is added
    or removed.
    • The profile responses gain invitedBy (#411 will show it), ratioWatch
      and ratioPolicy. The last two are what #417 reads.
    • Registration log entries gain inviter, sameIp and lastIpAccounts
      (#412 will show them).
    • The invite note on POST /profile/referral/create-invite and the rank
      permission set now carry invites_note (#413 will hide the note field
      without it).
  • /invite passes the rules variables through unchanged (#410). The api
    now resolves ${irc} to the public IRC page itself, so the page's override is
    gone. Nothing visible changes.

v0.9.11

Choose a tag to compare

@github-actions github-actions released this 30 Sep 17:46

Pairs with stellar-api 0.9.11, whose contract this re-vendors. No ui code
changes; deploy after the api.

Changed

  • The vendored api contract is stellar-api 0.9.11's. The api's changes are
    failure codes only, on seven operations: 404s on PUT /users/{id}/irc-nick
    and on adding a release tag, and 409s on stylesheet create and edit,
    release delete, and contribution create, for races its guard-coverage work
    now answers as a client error. No success shape changes.

v0.9.10

Choose a tag to compare

@github-actions github-actions released this 29 Sep 21:58

Pairs with stellar-api 0.9.10, whose contract this re-vendors. No ui code
changes; deploy after the api.

Changed

  • The vendored api contract is stellar-api 0.9.10's. The api's changes are
    failure codes only: six 409s for races its guard-coverage work now answers
    as a client error, and the 413 POST /asset has always answered. No
    success shape changes.

v0.9.9

Choose a tag to compare

@github-actions github-actions released this 29 Sep 00:56

Deploy order: stellar-api 0.9.9 first, then its image backfill, then this.
This release closes the CSP's img-src, font-src and connect-src (#402).
It depends on stellar-api 0.9.9's *Src fields (#403), and any remote image
the backfill has not imported renders as its link or the surface's default.
Run the backfill (node dist/scripts/backfill-remote-images.js in the api
container) and deploy this only on a clean report.

The image also serves the bundle from /etc/nginx/snippets/stellar-ui.conf
(#400), which stellar-compose's proxy includes from this release on.

Changed

  • The production CSP closes the resource axes (#402,
    stellar-api#457):
    img-src 'self' data:, font-src 'self', and connect-src 'self' plus
    the Sentry ingest origin. No page loads an image, font or connection from
    another host.
    • The Sentry origin comes from SENTRY_DSN at build time. A build with a
      DSN that is not an https URL now fails.
    • data: images stay, because the form controls draw their chevrons and
      ticks as data: SVGs, which make no request.
    • A member's external stylesheet can no longer load a remote image or
      font.
      style-src stays open for the sheet itself (ADR-0024), but
      img-src and font-src hold for whatever it references.
    • Deploy only after the stellar-api release carrying #742 and #745, and
      after its image backfill (#738) has reported clean.
    • The policy moves to webpack.csp.js. ADR-0003 is amended.
    • A new e2e test, P-14, fails on any CSP violation on a community, a
      release, a profile, a forum thread with an [img] and a form. The dev
      server emits no CSP, so it only bites against a production build, which is
      what stellar-compose's e2e runs.
  • The image serves the bundle from a snippet (#400,
    ADR-0011). The serving config
    moves to /etc/nginx/snippets/stellar-ui.conf: root, gzip, etag and the
    SPA fallback. default.conf now only wraps it in a server block, so the
    image run on its own behaves as before.
    • stellar-compose will include the same file in its proxy, instead of
      carrying its own copy, which dropped gzip
      (stellar-compose#58).
    • image/png is no longer gzipped, since PNGs are already compressed.
    • The build job now checks the running image: CSS arrives as text/css,
      JS arrives gzipped, and a deep link falls back to the index.
  • Every image is drawn from its resolved *Src field (#403,
    stellar-api ADR-0051).
    Avatars, release and group covers, collage mosaics, donor icons and the
    homepage features now render from avatarSrc, imageSrc, customIconSrc,
    secondAvatarSrc and coverImagesSrc. Each is a same-origin path, or null
    while a remote image is not imported, so no image loads from another host.
    • A pending or failed image shows the surface's default. The seeded test
      avatar still maps to its bundled image.
    • Edit forms keep reading and saving the raw fields, so a member's URL
      round-trips unchanged.
    • Needs a stellar-api release carrying #742 and #745. This clears the way
      for the CSP to close img-src (#402).
    • A new e2e test, P-13, gives the member a remote avatar and fails on any
      image request to another origin across the main pages and the profile.

v0.9.8

Choose a tag to compare

@github-actions github-actions released this 28 Sep 15:55

Added

  • ArtistPicker and TagPicker (#375): shared chip pickers in the UI kit, built on a generic ChipPicker, which uses downshift for keyboard handling and ARIA and renders only the kit's own field/chip/list/row hooks.

    • Suggestions arrive after a 250 ms pause, from two characters.
    • The artist picker holds { id, name } pairs and marks a removed artist.
    • The tag picker suggests from GET /tags and accepts free text as typed; the api stores the canonical form.
    • No page adopts them yet; #370 is the first consumer.
  • Contribution notification filters (#370, stellar-api#263): a member names the new contributions they want to hear about.

    • Filters page at /notification-filters: create, edit and delete filters within the rank's allowance. Artists and tags use the new pickers; deleting a filter asks first, because its matches go with it.
    • Matches page at /notification-filters/hits, plus one view per filter. Opening a match marks it read; loading the page marks nothing. Catch up and clear read act on the view in front of you.
    • Unread matches join the notification corner's count.
    • The header's Filters link shows only when the session's notificationFilterLimit is not 0.
    • Rank editor: a Notification Filters limit, a number plus an "Unlimited" checkbox, since null means unlimited and 0 means none.
  • Profile percentile tiles show the value behind each rank (#165, stellar-api#280/#722/#723).

    • Each tile's meta line reads #5 of 25 · 1.21 GB: bytes for contributed, consumed and bounty spent, counts for the rest. It is visible text, not a hover tooltip.
    • New Bounty Spent and Artists Added tiles, and an Overall tile last. Overall is a weighted score scaled by ratio, not a percentile, so it shows a bare number.
    • A tile the member has hidden is left out, and so is Overall unless contributed, consumed and ratio are all visible to you.
    • The panel is its own component, PercentileRankings.
  • Artist credits can be edited on the release page (#388, stellar-api#721). An Artists panel replaces the single-artist one.

    • Every credit is listed under its role heading, in role order.
    • Add is open to anyone who can see the release: pick an artist, choose a role (Guest by default), and Add. If there's no match, the typed name becomes "New: …", and the artist is created only when Add is clicked, so a typo never becomes a catalogue entry.
    • Edit reveals a role dropdown and a × on each credit you may manage: every credit for a moderator, otherwise only the credits you added. The last credit can't be removed.
    • ArtistPicker gains an opt-in allowCreate; the notification filters are unchanged.
  • Freepass and Neutralpass badges (#181, stellar-api#728). A ratio-exempt file carries a badge wherever files are listed: the release page's edition rows (and a collage entry's), the community release list, and My Contributions.

    • Freepass (success) and Neutralpass (info) are Badge chips, and each one's tooltip says what it means for your ratio. An ordinary file shows nothing.
    • A value the build does not recognise shows nothing, rather than breaking the row.
    • The community release list reads its rows from the contract, where it used to hand-type them. Badge takes an optional title.
  • Staff set Freepass and Neutralpass on the release page (#392, stellar-api#728). With contributions_manage (which admin implies), each edition row has a None / Freepass / Neutralpass select.

    • A change applies at once, with no confirmation: it is reversible, applies only to later downloads, and is audited.
    • The select is disabled while saving. On failure it shows the api's message and returns to the saved value.
    • Release page only. Setting a whole release or many releases at once is stellar-api#731.
  • The e2e suite runs in CI (#396). A new e2e workflow runs the Playwright suite against the published stellar-api image on a fresh Postgres, after POST /install and the api's e2e seed.

    • It runs against :latest (api main) on a push to main, nightly, and on a PR labelled e2e. It only reports there; it gates nothing.
    • A release tag runs it against the matching api version, and publish waits for it to pass.
    • A failed run uploads the Playwright report and traces, and the run summary names the api image digest.
    • Specs fixed for a fresh database: the release tests each open the release themselves instead of sharing a URL; P-07a now adds a format through the release page's [Add format]; P-07b fails rather than skips without a contribution; P-09 follows the invite page's current order.
    • P-08b's axe scan is held by a ratchet until #395: only the two known rules, and no more than 16 nodes.

Changed

  • Re-vendored the api contract: the session's rank carries notificationFilterLimit, a filter carries its artists by name (stellar-api#715), and POST /communities/{id}/members answers 204 (stellar-api#711).
  • Re-vendored the api contract for stellar-api#719: warned clears once a warning expires (shape unchanged), the session carries warnedUntil, and an invite-tree node carries donorRank.
  • Re-vendored the api contract for stellar-api#721/#722/#723: percentile blocks can be null and gain bountySpent, raw is always present, stats.buffer needs both sides visible, artistsAdded counts credits attached, and releases carry credits with the new credit routes and history actions.
  • Re-vendored the api contract from stellar-api 0.9.8: ReleaseHistoryEntry.action gains ratio_exempt_changed (stellar-api#732). The release page lists it by its summary, with no diff or revert, like the credit actions.

Fixed

  • A member's theme no longer outlives their session (#379). Logging out removes the theme and its stored href, so the public pages render the default. A pre-applied theme that fails to load, such as a registry sheet answering 401 after the session lapsed, removes itself. Previously a member logging back in could see the default theme until they refreshed, because the stale link was adopted with an unchanged href, which never refetches.
  • Checkboxes and radios show whether they are ticked (#368). @tailwindcss/forms drew them itself, and the field Role's background erased that drawing, so a ticked box looked unticked on every surface. Every checkbox and radio is now the browser's own control, tinted by the theme accent (accent-color: var(--st-accent)), with the shared themed focus outline. Classes that only styled the plugin's box are gone. Recorded as an amendment to ADR-0006.
  • Going to or from the home page no longer flashes the default theme (#161). Home had a layout instance of its own, so every crossing between it and another private page remounted the layout. Since #379 a remount removes the theme link and re-creates it, so a couple of frames painted in the default theme on every home crossing, Top 10 included when reached from home. / and every private path now share one route element, so the member's layout is one instance for the whole session. A visitor at / still gets the public landing; one anywhere else is still sent to /login.
  • Donor and warning signs follow a member wherever they are named as an author (#103, stellar-api#719), not only on their profile.
    • Where: forum post headers, comments, PM senders and participants, the inbox From column, and the staff inbox (requester, assignee, senders, and the queue's requester). They stay off the topic list and "last edited by" lines, where a name is metadata rather than authorship.
    • The donor sign is the member's tier, its badge in its colour, named in the tooltip. It reads donorRank alone, so an expired grant shows nothing even while isDonor lags behind it. The profile header and invite tree use the tier too, in place of a plain ♥.
    • The warning sign links to the rules. On your own name its tooltip says when your warning ends, or that it has no end; nobody else's expiry is shown or sent.

v0.9.7

Choose a tag to compare

@github-actions github-actions released this 24 Sep 00:54

Added

  • Privacy settings you can actually set
    (#367,
    stellar-api#586) —
    the Privacy tab now carries the five show* controls: email address,
    last-seen time, contributed stats, consumed stats, and ratio/buffer.

    These existed in the contract and were accepted by the api all along, but no
    control could ship: PUT /profile/me cascaded the paranoia level over them
    on every save, and this form posted a level every time — so five checkboxes
    would have arrived inert. api#586 removed the level, so a ticked box now
    reaches the database.

    Four preset buttons tick the boxes as a starting point, matching the levels
    members had before. They leave no trace: the server stores only the five
    booleans, so a preset is a convenience, never a mode you are stuck in.

Changed

  • The paranoia level radio is gone
    (#367,
    stellar-api ADR-0046) —
    UserSettings.paranoia no longer exists in the contract, so the four-level
    radio and its "Current: Level N" readback are replaced by the checkboxes
    above. No privacy setting changes value; the level was only ever a way of
    writing these five.

    showMatureContent stays where it is, on Appearance. It governs what you
    see rather than what others see of you, and that separation outlives the
    level it was originally excluded from.

  • The vendored contract catches up with stellar-api main — it picks up
    the registrationFull description added by
    stellar-api#657,
    which stopped conditioning reported capacity on registrationStatus.

    No generated type changes shape: src/types/api.ts gains one JSDoc
    line and nothing here needed rewriting. The sync is owed all the same, so
    that the vendored contract does not sit behind main untracked.

    It also retires a comment in Register.test.tsx that had gone false. The
    "closed site that is also full" case was written as a defensive check on a
    state the api could not then produce. The api produces it now, so that test
    covers a real state and its branch ordering is load-bearing.

  • The vendored contract picks up the seventh invite send gate
    (#358) —
    stellar-api#673 added
    registration_closed to the invite eligibility reason enum, and named the
    new refusal in the 403 on POST /profile/referral/create-invite.

    No component needed changing. InviteForm renders the api's msg and
    never switches on reason, which is what stellar-api's ADR-0043 intends —
    the api owns the words, so the member-facing string arrives for free.

  • The vendored contract catches up with stellar-api 0.9.7
    (#358) — it picks up
    two api changes that need no UI code yet:

    • POST /communities/{id}/members takes an optional role
      (stellar-api#709).
      It defaults to consumer, which is what the current form admits. The
      picker that sends it is
      #373.
    • GET /contributions/{id} no longer declares a 403
      (stellar-api#700).
      A contribution the viewer cannot see is now the same 404 as a missing
      one. Nothing here branched on that 403.

    The notification-filter operations #358 listed were already vendored by
    #372. This sync clears
    the rest of that drift.

Fixed

  • The invite key from the emailed link now reaches the form
    (#359,
    #360) — the invite
    email carries the key only inside /register?inviteKey=…, and nothing
    here read the query string. Every invited member landed on an empty,
    required Invite Key field and had to pick the key out of their own
    address bar. That is the whole invite flow on an invite-only site.

    The field is now seeded from the link and stays editable. Mail clients wrap
    and truncate long URLs, so a member who can see a mangled key is one who can
    correct it — which is why the key is prefilled rather than used invisibly,
    as the recovery token is.

    The key is also sent on an open site, where no field is rendered to hold
    it. Since
    stellar-api#675 an
    open site honours a presented key and records who invited whom, instead of
    losing the relationship; a bad key is harmless there, because the api drops
    the edge rather than refusing the registration. An open site still shows no
    invite field, since a member arriving without a key should not be left
    wondering what they are missing.

    A link with no key behaves exactly as before.

  • A closed comment thread says so, instead of offering a form that fails
    (#371,
    stellar-api#697,
    stellar-api#701) —
    the api now answers 404 for a thread on a page the viewer cannot see, or on
    a deleted page. The comments panel showed "No comments yet." with a live
    form, and posting failed. It now reads "Comments are closed." with no form.
    Staff see this on a deleted collage they can still open.

  • Featured shelves on a profile open for every viewer
    (stellar-api#706) —
    a personal collage linked from someone else's profile answered 403. The api
    now lets any member read one; changing it is still for its owner or staff.
    This needed no UI code, only the re-vendored contract.

Security

  • Sentry no longer receives the query string of any URL
    (#361) — two routes
    here carry a credential there: /recovery?token=…, a password-reset token
    that grants account takeover, and /register?inviteKey=…. The browser
    client attaches the full page URL to every captured event, the previous
    page's URL as a Referer header, and URLs to navigation and network
    breadcrumbs. Nothing removed them, so an error captured anywhere near either
    page sent a live credential to a third party, where it is retained and
    searchable.

    All four are stripped from the first ? now. The path survives, so an event
    still says which route it came from.

    The Referer header mattered most and is the easiest to miss. It holds
    the page before this one, so a member who registers and lands on the
    homepage carries the invite key into the next error thrown there — scrubbing
    only the current URL would have left the common case open.

    Query strings are stripped wholesale rather than by name. stellar-api
    redacts its one credential parameter by name; doing that here would
    reproduce the bug being fixed, since this gap exists precisely because
    nobody revisited the scrubber when a second credential-bearing URL appeared.
    The query strings in this app are browse filters and page numbers.

    beforeSendTransaction is wired to the same function. It does nothing today
    — no sample rate is set, so no spans are sent — but the same integration
    writes full URLs onto spans, and enabling tracing later would otherwise
    reopen this silently.

v0.9.6

Choose a tag to compare

@github-actions github-actions released this 20 Sep 22:34

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 s...

Read more

v0.9.5

Choose a tag to compare

@github-actions github-actions released this 16 Sep 16:43

There is no 0.9.4. The patch moved two, from 0.9.3, so that
stellar-compose can pin stellar-api:0.9.5 and stellar-ui:0.9.5 together and
tag that commit as a stack version — compose tags only commits where both pins
name the same release, and leaves an unequal pair untagged rather than giving it
an invented number. Per ADR-0004
the patch digit is this repo's own cadence, so the skip costs nothing here; the
next UI-only release is 0.9.6 against the same 0.9.5 contract.

Added

  • A release group panel on the release page
    (#317) — "this album,
    everywhere you can reach it": the group's canonical title and year, and the
    member releases in other communities, each linking to its own release page.
    The release being viewed is marked, not hidden, so the panel reads as the
    whole album rather than a list of elsewhere.

    Identity and membership arrive separately, and the split is an access boundary
    rather than a fetching convenience. release.group inlines with the release
    read — seeing a release entitles you to its group's identity — so the header
    renders with no request. The sibling list stays behind
    GET /release-groups/{id}, which returns only the members this viewer may
    see
    . That list can be shorter than the album really is and can differ
    between two viewers on the same page; both are correct, not gaps.

    The endpoint answers 404 for a group with no viewer-visible member, using
    the same status and message as a group that does not exist. It cannot arise
    from this page, so it renders as no panel rather than an error banner.

    The panel does not render for an ungrouped release, and fires no request for
    one — which is most of them, since grouping is opt-in and never backfilled.

  • A collage row that stands for two entries says so, and removing it removes
    the album
    (#319) —
    GET /collages/{id} now collapses entries that are the same album catalogued
    in two communities. The surviving row carries a copy-count chip, and expanding
    it lists the absorbed copies above the edition stack, each linking into its
    own
    community.

    × now means "remove the album", not "remove this row". Deleting only the
    representative left the absorbed copy behind, to resurface as its own row on
    the next load. The deletable copies are computed before anything is sent,
    from the same per-row permission the button itself uses — the collage owner,
    that row's own adder, or collage staff — so the confirm states the outcome up
    front ("you can remove 1 — the rest were added by other members and will
    stay") and no request is fired that is already known to be refused.

    Deletions run one at a time and stop on a 429 rather than firing the
    remainder into the same rate limit. The catch is kept regardless: 403 also
    covers a locked collage, which the client-side predicate cannot see.

    The absorbed copies are shown by title and linked by community id. The collage
    response carries no community name — for the entry or its copies — and
    naming them would cost a request per expanded row.

  • Search can collapse duplicates onto the album
    (#320) — a Show releases · albums toggle on the release browse page. In album mode the row
    is the group, so an album held in three communities is one result and
    total counts albums; each row lists the versions this viewer can reach.

    It is a separate endpoint rather than a flag because /search/releases
    paginates with skip/take: collapsing a fetched page there would leave
    total overstating and show a group straddling a page boundary twice.

    Every filter carries across untouched — the two endpoints accept identical
    ones — but the sort vocabularies differ, so consumers, contributors
    and random are reconciled to the group default on toggle rather than sent,
    since the group endpoint answers 400 for them. There is deliberately no
    member-count sort: the api can only count a group's whole relation, not the
    part the viewer may see.

    An empty album search names its own cause — grouping is opt-in and never
    backfilled, so on an uncurated catalogue every album query is empty — and
    links back to the same filters as releases. The shared "No releases found."
    would be wrong for the majority of queries rather than an edge case.

  • Staff can search the invite pool by email and cancel a pending invite
    (#329,
    api#636).

    • Search is a case-insensitive substring, sent 300ms after typing stops
      and only at 3 characters or more, the api's minimum. It combines with the
      status filter, and Clear resets both.
    • Cancel appears on pending rows for staff with invites_edit (or
      admin). A modal names the address and takes a required Reason (staff
      only, audit log) and an optional Message to inviter (a System PM; blank
      notifies nobody). The api refunds the inviter when the invite was spent.
      A 409 or 404, meaning the invite was accepted, lapsed or cancelled
      first, shows the api's msg and refreshes the list. On existing installs
      no rank carries invites_edit until an admin grants it.
    • A Sent column shows when each invite was last sent (createdAt).
  • Staff can revoke a member's invites and set their invite balance from the
    profile
    (#329,
    api#636). A new
    Invites panel sits under Change Rank in Staff Actions, showing the
    balance and whether invite privileges are allowed or revoked.

    • Revoke invites / Restore invites opens a dialog that says what the
      change does, then takes a required Reason (staff only, audit log) and an
      optional Message to member (a System PM; blank notifies nobody). The
      direction is fixed when the dialog opens.
    • Edit balance takes a new balance from 0 to 1000 and sends the balance on
      screen as expectedInviteCount. Save stays disabled while the value is
      unchanged. A 409 means the balance moved first: the dialog stays open with
      what staff typed, waits for the reloaded profile, and names the new balance.
      Nothing is retried automatically. The dialog reads the balance from its own
      subscription to the cached profile, so the notice, Current balance and
      the expectedInviteCount a save sends are always the same number, and they
      follow a background refresh.
    • The panel needs invites_edit and a profile that carries both invite
      fields. The api sends them only to staff it recognises by staff, admin,
      users_edit, users_warn or users_disable, so a rank holding
      invites_edit alone sees no panel. On existing installs no rank carries
      invites_edit until an admin grants it.
    • The profile sidebar marks a revoked member's balance (revoked), for the
      member and for staff. The balance is kept while revoked.
  • The invite page explains the rules, the gates, and what you have pending
    (#329,
    #331,
    api#637,
    api#640). /invite
    now reads rules, send, pending invites, then the invite tree.

    • The Golden Rules are quoted, not paraphrased. Rules 1.1, 2.1 and 2.2
      come from GET /rules/tree and render through utils/rulesText.tsx with
      the tree's variables, replacing two hard-coded paragraphs. They are
      selected by their stable code and numbered by position, so a reordered
      tree renumbers them rather than quoting the wrong rule.
    • A refused sender is told before typing. The page reads
      GET /profile/me/invites/eligibility and, when it refuses, shows the api's
      own msg in place of the form; a trailing site path in that message is
      rendered as a link. A send still surfaces its own 403, which remains the
      authority. If the eligibility read itself fails, the form is shown: the
      POST decides.
    • Pending invites lists what has not been accepted yet, soonest to lapse
      first, with the note, when it was sent and when it expires. Withdraw
      confirms first, saying the link stops working immediately and that the
      address cannot be invited again until the original expiry, then reports the
      api's msg — which is what says whether the invite came back to you.
    • The header count follows a send or a withdraw, which it did not before,
      and shows ∞ for a sender holding invites_unlimited (or admin). The
      old ∞ branch keyed on a null count, which the api never sends. An
      unlimited sender's page says "Invites: unlimited" instead of a balance.
    • ${irc} in rule 1.1 points at the public IRC guide rather than /irc, a
      route this app does not have
      (api#630).
  • A date in the future reads as the future. readableTime counted only
    backwards, so anything ahead fell into its "just now" branch. It now reads
    in 2d, which also fixes the staff invite pool's Expires column, where
    every pending invite claimed to expire "just now".

  • A disabled account is told where reactivation happens
    (#324,
    api#622). Signing in
    to a disabled account showed a four-word toast that then vanished. The login
    form now keeps a panel that names the IRC channel to ask in a...

Read more