summaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAge
* Add the following attributes to all markdown-generated links:Owen Jacobson2025-04-21
| | | | | | | | * `target="_blank"`: when Pilcrow is running in a browser, clicking a link should not replace Pilcrow with the target of the link. Pilcrow is "app-like" enough that opening links in a new tab _by default_, without user intervention, is likely more appropriate. * `rel="noreferrer"`, which (A) stops most UAs from setting a referrer header when following those links, and (B) also implies `noopener`, preventing the link target from using `window.opener` from reaching back into Pilcrow's DOM. I briefly experimented with DOMPurify's `RETURN_DOM_FRAGMENT` mode, which would have made the tests somewhat easier to write, but I wasn't able to find a good way to integrate the returned `DocumentFragment` objects with Svelte components, so HTML-as-strings it is. Sigh.
* Use a heartbeat to allow the client to reconnect after network failures.Owen Jacobson2025-04-10
|\
| * Restart the event connection if heartbeats stop showing up.Owen Jacobson2025-04-08
| | | | | | | | | | | | | | | | The changes introduced in the previous commit make it possible to detect lost connections and restart them, so do so. The process is pretty simple - a new remote state is spun up using `/api/boot`, swapped in for the existing state, and a `new EventSource` is started from that new remote state to consume events. This can induce some anomalies. For example, messages that arrive on the server between the loss of one connection and the creation of the next one just "show up" in boot, without ever appearing in the event stream. (This is technically also true on client startup, but it's easier to expect in that situation.) This is something we'll need to consider when implementing things like notifications or unread flags, though the ones we have today, which are state-based, do work fine. By design, this _does not_ retry either the `/api/boot` call or the new event source setup. Event sources will try to reconnect on their own, up to a point, so that's fine, but we need to build something more robust for `/api/boot`. I want to tackle that separately from detecting lost connections and reacting to them, but that does mean that this is not a complete solution to client reconnects.
| * Heartbeats are part of the event protocol.Owen Jacobson2025-04-08
|/ | | | | | | | | | | | | | | | | | | A heartbeat is an event that the server synthesizes any time an event stream has been idle for longer than some timeout. They allow clients to detect disconnection and network problems, which would otherwise go unnoticed because event streams are a one-way channel. Most network problems only become clear when the offended party tries to _send_ something, and subscribing to an event stream only sends something during the request phase. Technically, Pilcrow has always sent these, since we started using Axum's SSE support: it defaults to sending a dummy event after 15 seconds (consisting of `":\n\n"`, which is then ignored). I've built Pilcrow's heartbeat support out of that, by customizing the event sent back. The results _mostly_ look like existing events, but there are two key differences: * Heartbeats don't have `id` fields in the event stream. They're synthetic, and they don't participate in either the "resume at" sequence management, or the last-event-id header-based resumption management. * Heartbeats have an `event` but no `type` field in the message body. There are no subtypes. To make it less likely that clients will race with the server on expiring timeouts, heartbeats are sent about five seconds early. In this change, heartbeats are due after 20 seconds, but are sent after 15. If it takes longer than five seconds for a heartbeat to arrive, a client can and should treat that as a network problem and reconnect, but I'd really like to avoid that happening over differences smaller than a second, so I've left a margin. I originally sketched this out in conversation with @wlonk as having each event carry a deadline for the next one. I ultimately opted not to do that for a few reasons. First, Axum makes it hard - the built-in keep-alive support only works with a static event, and cannot make dynamic ones whose payloads might vary (for example if the deadline is variable). Second, it's complex, to no apparent gain, and adds deadline information to _every_ event type. This implementation, instead, sends deadline information as part of boot, as a fixed interval in seconds. Clients are responsible for working out deadlines based on message arrivals. This is fine; heartbeat-based connection management is best effort at the best of times, so a few milliseconds of slop in either direction won't hurt anything. The existing client ignores these events entirely, which is convenient. The new heartbeat event type is defined alongside the main event type, to make it less likely that we'll inadvertently make changes to one but not the other. We can still do so advertently, I just don't want it to be an accident.
* Remove stray debug outputOwen Jacobson2025-04-04
|
* Merge branch 'prop/minor-cleanups'Owen Jacobson2025-04-03
|\
| * Hopefully make the "no control characters" criterion for names easier to follow.Owen Jacobson2025-03-24
| |
| * The label used to mask "Secret" strings in ↵Owen Jacobson2025-03-24
| | | | | | | | | | | | 357116366c1307bedaac6a3dfe9c5ed8e0e0c210 wasn't updated (and wasn't quite correct then, either). I haven't found a way to derive it from the name of the type.
* | Merge branch 'prop/rename-login-to-user'Owen Jacobson2025-04-03
|\ \
| * | Rename a bunch of straggler references to `login`.Owen Jacobson2025-03-24
| | |
| * | Rename `login` to `user` in the client.Owen Jacobson2025-03-24
| | |
| * | Update the API docs to describe `user`s, not `login`s.Owen Jacobson2025-03-23
| | |
| * | Rename `login` to `user` throughout the serverOwen Jacobson2025-03-23
| | |
| * | Change the prefix for newly-generated user IDs to `U`, for `User`.Owen Jacobson2025-03-23
| | |
| * | Rename the `login` module to `user`.Owen Jacobson2025-03-23
| | |
| * | Rename `user` to `login` at the database.Owen Jacobson2025-03-23
| |/
* / Decode stored channel state as JSON. (Oops.)Owen Jacobson2025-04-03
|/
* Expire messages after 30 days.Owen Jacobson2025-03-23
| | | | In a discussion with wlonk, we both agreed that 15 days is _too_ aggressive, but also that it's not quite time to implement configurable expiry.
* Merge branch 'prop/restartable-state'Owen Jacobson2025-03-23
|\
| * Track state on a per-session basis, rather than via globals.Owen Jacobson2025-02-26
| | | | | | | | | | | | | | | | | | | | | | Sorry about the thousand-line omnibus change; this is functionally a rewrite of the client's state tracking, flavoured to resemble the existing code as far as is possible, rather than something that can be parted out and committed in pieces. Highlights: * No more `store.writeable()`s. All state is now tracked using state runs or derivatives. State is still largely structured the way it was, but several bits of nested state have been rewritten to ensure that their properties are reactive just as much as their containers are. * State is no longer global. `(app)/+layout` manages a stateful session, created via its load hook and started/stopped via component mount and destroy events. The session also tracks an event source for the current state, and feeds events into the state, broadly along the same lines as the previous stores-based approach. Together these two changes fix up several rough spots integrating state with Svelte, and allow for the possibility of multiple states. This is a major step towards restartable states, and thus towards better connection management, which will require the ability to "start over" once a connection is restored.
| * Split Markdown rendering out into its own JS module.Owen Jacobson2025-02-25
|/
* Merge remote-tracking branch 'origin/prop/fix-scroll'Owen Jacobson2025-02-25
|\
| * Don't scroll all the time as messages are addedKit La Touche2025-02-25
| | | | | | | | Only once on load, then once per new message.
* | Remove forced `are you sure` prompt when navigating away.Owen Jacobson2025-02-25
|/
* Merge branch 'prop/preserve-state'Owen Jacobson2025-02-24
|\
| * npm run fmtOwen Jacobson2025-02-24
| |
| * Move constant closer to usageOwen Jacobson2025-02-24
| |
| * Only redirect to last active channel off of /Kit La Touche2025-02-21
| |
| * Merge branch 'main' into prop/preserve-stateKit La Touche2025-02-21
| |\
| * | Only check for message visibility while a channel is actually attached to ↵Owen Jacobson2025-02-20
| | | | | | | | | | | | | | | | | | the DOM. Prevents this from breaking during DOM unmounting, when leaving a channel.
| * | Remove explanatory commentKit La Touche2025-02-20
| | | | | | | | | | | | That no longer vitally pertains.
| * | Remember last active channel and navigate there on root loadKit La Touche2025-02-20
| | | | | | | | | | | | To facilitate PWA behaviour.
| * | Separate channel metadata out into its own storeKit La Touche2025-02-20
| | | | | | | | | | | | | | | | | | | | | | | | This is stored locally, and, while parallel to channel info, is not the same as. Eventually, this may hold info about moot/decayed channels, and grow unbounded. That'll need to be addressed.
| * | Merge local channel data and remote to maintain stateKit La Touche2025-02-20
| | | | | | | | | | | | | | | | | | | | | | | | | | | When we hit the boot endpoint, we get the server's view of things. If we just setChannels with that, we overwrite all our locally-stored info in on things like lastReadAt. So we need to merge data. Eventually, this might militate for a `meta` key containing an object of locally stored data, rather than having to handle each key specially.
* | | Merge branch 'prop/global-state-at-top-level'Owen Jacobson2025-02-24
|\ \ \ | |_|/ |/| |
| * | Hoist global state access out of individual components.Owen Jacobson2025-02-21
|/ / | | | | | | | | | | | | | | Access to "global" (maybe "external?") state is now handled at the top level of the component hierarchy, in `+page.svelte`, `+layout.svelte`, and their associated scripts. State is otherwise passed down through props, and changes are passed up through callbacks. This is - hopefully - groundwork for refactoring state management a bit. I wanted to move access to state out to a smaller number of places, so that I have fewer places to update to implement reconnect logic. My broader goal is to make it easier to refactor these kinds of external side effects, as well, though no such changes are in this branch. This change also makes testing a mile easier, since tests can interact with props and callbacks instead of emulating the whole HTTP request stack and the Pilcrow API. This change removes do-very-little tests.
* | Ensure `must_use` warnings fire even after results are unwrapped.Owen Jacobson2025-02-21
| |
* | Remove stray importsOwen Jacobson2025-02-21
| |
* | Retire use of $page store in favour of Sv5 page stateOwen Jacobson2025-02-21
| |
* | Be a little more pedantic about constant str ref lifetimesOwen Jacobson2025-02-21
| |
* | Reorder impl to match traitOwen Jacobson2025-02-21
| |
* | Add missing awaits on goto() callsOwen Jacobson2025-02-21
| |
* | Fix invalid JSONOwen Jacobson2025-02-21
| |
* | Be a bit more careful with the nesting of anchors and list items.Owen Jacobson2025-02-21
| | | | | | | | Browsers cope with weird nestings mostly fine, but there's no upside for us in testing that.
* | Remove unused type attribute on textareaOwen Jacobson2025-02-21
| |
* | Remove duplicate `content` hack from reset.Owen Jacobson2025-02-21
| | | | | | | | | | | | According to <https://stackoverflow.com/a/6803278>, this was needed for old versions of Safari. However, since at least 2022, Safari has supported `content: none` just fine. Related Safari bug (still open as of this writing, comments relevnat): <https://bugs.webkit.org/show_bug.cgi?id=20032>
* | Provide fallback generic fonts for our custom fontsOwen Jacobson2025-02-21
| |
* | Remove nonexistent CSS variableOwen Jacobson2025-02-21
| |
* | Remove references to nonexistent italic variants of FiraCodeOwen Jacobson2025-02-21
| |
* | Write down a brief mission blurb.Owen Jacobson2025-02-21
| |