Skip to content

v1.35.0

Choose a tag to compare

@SysAdminDoc SysAdminDoc released this 19 Aug 07:31
· 209 commits to main since this release

Added

  • The timeline can stop. Set a number of posts and the feed stops extending there, with a
    control to continue that releases another page of the same size. Off by default; zero leaves X's
    endless scroll exactly as it was. It applies to feeds that extend — home, profile, search — and
    never to a conversation, which is finite already. The property the implementation is built around
    is that nothing already on screen moves: the control is inserted after the last post shown
    rather than above the cut, so continuing extends downward into space that was below the fold, and
    a post the reader was looking at stays exactly where it was. A release granted on one feed does
    not carry to the next.

  • A filtered post can say what caught it. A filter that hides silently is hard to tell from a
    bug, so a suppressed post now names the rule, keyword, pattern, engagement floor or media type
    responsible — by the rule's title where it has one. The reason comes out of the same evaluation
    that made the decision rather than being worked out afterwards, because a reason computed
    separately is a second implementation of the filter and the two would eventually disagree about a
    post the reader is looking at. Nothing is stored: the sentence lives on the article as an
    attribute the stylesheet reads with attr(), and structural reasons are written into the
    stylesheet itself, so no post costs anything extra. The default names reasons on dimmed posts,
    which changes no layout; the third setting also turns a hidden post into a one-line strip that
    opens on hover or when tabbed into — the post stays clipped rather than having its children
    removed, so it remains reachable by keyboard.

  • A filter rule can carry a name and a lifetime. Start a line with [a title] to name a rule,
    and give it for 7d from <instant> to have it apply only for a while. An expired rule stops
    filtering on the next pass with no settings change and no reload — and is never deleted: the
    panel lists what expired and offers one control that restarts each rule's own window from now.
    The window is stored as its duration plus its start rather than as a deadline, which is what
    makes renewing possible without asking the user again what they originally chose. Rules written
    before any of this keep parsing exactly as they did.

Fixed

  • A feature module that nothing registers is now a test failure. The boot test checked a
    hand-written list of ten feature ids, so a new module could be written, wired to a setting, tested
    against directly, and never loaded by the app — which is how the timeline-stop feature first
    landed. The check now reads every FeatureModule id out of src/features and requires the booted
    registry to hold all thirty-four.

  • Eleven sentences in the Control Center were shipping in English in all eight locales while
    translation coverage reported 100%.
    The extractor draws the panel to harvest its copy, so a row
    that only appears under a condition — an expired rule, a callback the harness cannot supply — is
    reached only by the source scan, and that scan read one string per row: the label. Every such
    row's explanatory sentence was invisible to it. The scan now reads the description too, for each
    helper whose second argument is one, and a test drives the harvester over a conditional row so
    the gap cannot reopen.

  • Filter on the shape of a post, not only its words. Two predicates, both off by default.
    Quote posts hides or dims a post that quotes another — structural, so it is a :has() rule
    costing nothing per post, and it tells a quoted post from a link preview by the quoted author's
    name rather than by the role="link" the two share. Low-engagement posts hides or dims a post
    under a minimum number of replies, reposts or likes; the count is read from the action button's
    own accessible name, which carries the exact figure where the visible text is rounded to "11K",
    and which reads zero as zero where the visible text is simply empty. A post whose count cannot be
    read is never filtered on it, and a minimum of zero reads no counts at all.

Changed

  • The media and verified filters are stylesheet rules now, not work done per post. Both were
    questions about the shape of a post's own subtree — does it contain a photo, a video player, a
    GIF, the verified badge — and the engine answered them with five querySelector calls against
    every article on every mutation batch. They are emitted as :has() rules instead, from one table
    that the remaining JS reader shares, so a timeline scroll no longer pays for them at all. Two
    consequences beyond the saved work: media that renders after a post was judged is now caught,
    where the JS pass had stamped that post done and would never look at it again; and a post the
    engine has not judged yet is left alone, so nothing flashes hidden before the allowlist is
    consulted. Precedence is unchanged — an allowlisted author still outranks everything, a rule that
    dims still beats a media rule that would hide, and hiding video still hides GIFs.