summaryrefslogtreecommitdiff
path: root/ui
Commit message (Collapse)AuthorAge
* Create swatch for the `Invite` component.Owen Jacobson2025-07-08
|
* Event capture and display tools.Owen Jacobson2025-07-08
| | | | | | This is meant to be used in swatches, to display the events and callbacks generated by a component as part of the swatch. The usage pattern is described in the comments (in both places). Naturally, this has its own swatch.
* Create "derivers," as an exception-free option for working with structured ↵Owen Jacobson2025-07-08
| | | | | | | | | | | | | | | | | | | data in swatches. This is meant to be used alongside `$derive`, for inputs with complex structure. For example: ```js let jsonInput = $state('{}'); let json = $derived(deriver.json(jsonInput)); // … <textarea bind:value={jsonInput}></textarea> ``` This allows textual editing of the data, while preventing exceptions due to syntax or logical errors in partially-edited data from breaking Svelte's derive process (see comments). Note that these exceptions are not considered [unexpected errors] by SvelteKit, because they do not arise "while handling a request;" they are considered errors by Svelte, but Svelte doesn't appear to provide any affordances for handling errors in this context, so we have to bring our own. [unexpected errors]: https://svelte.dev/docs/kit/errors#Unexpected-errors
* Set up a skeleton for swatches.Owen Jacobson2025-07-08
| | | | | | | | | | | | | | | | | | | A swatch is a live, and ideally editable, example of an element of the service. They serve as: * Documentation: what is this element, how do you use it, what does it do? * Demonstration: what does this element look like? * Manual test scaffolding: when I change this element like _so_, what happens? Swatches are collectively available under `/.swatch/` on a running instance, and are set up in a separate [group] from the rest of the UI. They do not require setup or login for simplicity's sake and because they don't _do_ anything that requires either of those things. [group]: https://svelte.dev/docs/kit/advanced-routing#Advanced-layouts-(group) Swatches are manually curated, for a couple of reasons: * We lack the technical infrastructure needed to do this based on static analysis; and * Manual curation lets us include affordances like "recommended values," that would be tricky to express as part of the type or schema for the component. The tradeoff, however, is that swatches may fall out of step with the components they depic, if not reviewed regularly. I hope that, by making them part of the development process, this risk will be mitigated through regular use.
* Remove the (entirely unused and unusable) `body` property from `Message`.Owen Jacobson2025-07-08
|
* Rename "channel" to "conversation" throughout the client.Owen Jacobson2025-07-03
| | | | Existing client state, stored in local storage, is migrated to new keys (that mention "conversation" instead of "channel" where appropriate) the first time the client loads.
* Move the `/ch` channel view to `/c` (for conversation).Owen Jacobson2025-07-03
|
* Replace `channel` with `conversation` throughout the API.Owen Jacobson2025-07-03
| | | | This is a **breaking change** for essentially all clients. Thankfully, there's presently just the one, so we don't need to go to much effort to accommoate that; the client is modified in this commit to adapt, users can reload their client, and life will go on.
* Boot the client by consuming events.Owen Jacobson2025-06-20
| | | | We use the same event processing glue that the client has for keeping up with live events, which means that a significant chunk of state management code goes away entirely.
* tools/reformatOwen Jacobson2025-06-11
|
* Suppress testing of the MessageInput's, uh, text input.Owen Jacobson2025-05-22
| | | | We … can't test this, I think, because of a bug in `user-event`. Maybe there's an alternative that directly manipulates the DOM, but I'd prefer not to do that.
* Use browser-native methods to hide elements, not CSS alone.Owen Jacobson2025-05-22
| | | | The hidden `textarea` used to attach the form value to the DOM was being included in the ARIA accessibility tree, at least in testing (I didn't check in a browser). While we could suppress this iwth `aria-role="hidden"`, the WHATWG recommendation is to Not Do That, and to find another way to hide the element, instead. Marking the element as hidden accomplishes that goal, _and_ gets rid of a style rule.
* Merge branch 'prop/unread-channels'Owen Jacobson2025-05-16
|\
| * Avoid converting DateTime values into numbersOwen Jacobson2025-05-15
| |
| * Fix up spots where we still tried to treat `remote.channels.all` as a map.Owen Jacobson2025-05-15
| | | | | | | | | | | | | | | | | | | | In ae93188f0f4f36086622636ba9ae4810cbd1f8c9, `remote.channels.all` became a flat array of channels, instead of a map, in order to simplify some of the reasoning around how state changes propagate. However, I neglected to remove all Map-shaped calls referring to it. This lead to some pretty interesting behaviour: * The client could not track unread state, because reconciling local state against the remote state would find no remote state, then throw away local state entirely as a result. * The client would not actually update when a new channel appeared. * The client would not actually update when a channel disappeared.
* | Merge remote-tracking branch 'codeberg/prop/unread-channels'Owen Jacobson2025-05-16
|\|
| * Don't try to update last read time for channel pages if the channel has ↵Owen Jacobson2025-05-14
| | | | | | | | | | | | vanished. This may happen if the user has a link to a channel open when the channel is deleted/expires, or if they return to the app after the last channel they looked at has expired.
| * Move derivation of the synthesized view of channels (and messages) into ↵Owen Jacobson2025-05-14
| | | | | | | | `session`.
| * When there's no message in view, update last read time based on the ↵Owen Jacobson2025-05-14
| | | | | | | | channel's creation time.
| * Consider when a channel was created as part of determining whether it has ↵Owen Jacobson2025-05-14
| | | | | | | | been read.
| * Track created-at times for each channel.Owen Jacobson2025-05-13
| |
* | Merge remote-tracking branch 'codeberg/prop/text-input-no-quill'Owen Jacobson2025-05-16
|\ \ | |/ |/| | | | | | | > We can hand-write markdown for now, as per discussions. > > If we have buttons and shortcuts, we'd like them to insert actual markdown into the text stream, and then, as a separate concern, we'd like to render the markdown without changing the text stream (à la Discord). But we're doing none of that now, and it's too high a piece of fruit to pluck today.
| * Move placeholder-related CSS into `textarea.css`.Owen Jacobson2025-05-15
| |
| * Restore the placeholder when the editable input is emptied out after ↵Owen Jacobson2025-05-13
| | | | | | | | | | | | modification. This also avoids using `placeholder` on elements where it's nonstandard, like `<div>`s.
| * Support non-mouse accessibility.Owen Jacobson2025-05-13
| | | | | | | | | | * Give the input `div` a marker to tell screen readers &c that it is a textbox. * Ensure that it participates in tab order. (Zero is a sentinel value, see <https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex>.)
| * Make the resulting form more amenable to normal DOM operations.Owen Jacobson2025-05-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | This is purely an aesthetic choice: * The DOM `reset()` function can be used to clear the form, but can't be used to clear editable DIVs. Binding the editable div to a (hidden) form field allows `reset()` to clear both. * We can find the target `form` element out of the event, but the API needed to do so differs between events dispatched to form controls and events dispatched to random DOM nodes. Using `closest('form')` works for both kinds of event target. In practice, there is little need to make sure the message input form uses "normal" DOM APIs for functional reasons. Everything inside `MessageInput` is controllable through the component's script. This change isn't based on a functional need, but rather in the hopes that integrating with the DOM APIs makes it easier for _code we don't control_ - screen readers, password managers, saved-form support in browsers, &c - to integrate with Pilcrow. It is purely speculative. (This also used to be necessary because Firefox didn't support `contenteditable="plaintext-only"`, but [support was added in March][ff-pto] [ff-pto]: https://www.mozilla.org/en-US/firefox/136.0/releasenotes/#:~:text=The%20value%20plaintext%2Donly%20can%20now%20be%20specified%20for%20the%20contenteditable%20attribute%2C%20making%20the%20raw%20text%20of%20an%20element%20editable%20but%20without%20supporting%20rich%20text%20formatting.
| * Implement the disabled state:Owen Jacobson2025-05-13
| | | | | | | | | | * Suppress input (including paste) while the input is disabled. * Style the input to make it visible that it's not accepting input.
| * Dim out placeholder text.Owen Jacobson2025-05-13
| | | | | | | | | | | | It's not much, but it makes it a bit easier to see that the placeholder text _is_ a placeholder. Not sure what to do about it vanishing permanently once the element is edited, until the element is formally `reset()`, though.
| * Send multi-line messages as multiple lines.Owen Jacobson2025-05-13
| | | | | | | | For whatever reason, `innerText` captures interior line breaks, while `textContent` does not. Wild. DOM APIs.
| * Fix up input to be a contenteditable divKit La Touche2025-05-11
| |
* | Remove code block borders.Owen Jacobson2025-05-08
| | | | | | | | This styling suggestion c/o Jessamyn Smith! Thanks, Jess. I like it.
* | Make rules targeting inline code more specific.Owen Jacobson2025-05-08
| | | | | | | | This prevents the need to go and un-apply unwanted styles when considering code blocks.
* | Prevent double-indenting of the first line of a code block.Owen Jacobson2025-05-08
| | | | | | | | | | | | | | | | | | | | | | | | For reasons known only to the author, marked emits code blocks as <pre><code>your code here</code></pre> Inline code, on the other hand, is emitted as <p>Non-code text <code>code text</code> non-code text.</p> In d15bfb2b9a4872cba99bc966fe5c9c4399b3323c, we added a rule to give inline code nicer leading and trailing space, so that the borders don't directly abut the letters. However, we neglected to consider code blocks; the padding added for inline code also affected their first line, pushing it in slightly. This removes the padding from `<code>` when it is a direct child of a `<pre>`, as per the markup emitted by marked.
* | Stlye headings, as well.Owen Jacobson2025-05-08
| | | | | | | | | | | | This is a fairly simple approach using a linearly-reducing scale (from 2.25 down to 1.0) to adjust both the font size and the line height. All headings are bold, and are in the body typeface. People who actually use headings in _chat messages_ are doing a bit, but hey, bits are valid.
* | Fix horizontal padding on inline codeKit La Touche2025-05-08
| |
* | Apply rounded borders to inline code inside list elements.Owen Jacobson2025-05-08
| | | | | | | | There isn't a good way to target "any <code> not inside a <pre>" so we do a little reverse logic here, as a treat.
* | Provide conventional text styling for message bodies.Owen Jacobson2025-05-08
|/ | | | | | | | | | | | | | | | | | Our reset strips out a ton of default browser styles, which is good, but we haven't yet gotten around to adding styles we're using. This left formatted messages feeling a lot like this regardless of the intended formatting. (Is the above an example of a paragraph? A list? A single line that has gotten wrapped? The answer was "yes.") To make the margins and padding work out nicely, I've rearranged the positioning containers used for message runs and messages. We also no longer `float` the message handles, since we no longer need to: they can be positioned relative to the message they're part of. Styling on long bodies of inline code (`like this`) is a bit shaky. The outline overlaps with the following line. I think having a visual cue for where the code block begins and ends is _good_, but I'd like to pick apart some of the other examples on the internet because I think this needs more work. This change also makes code blocks wrap lines at the page edge where possible (it'll still scroll if wrapping isn't possible). This won't affect _most_ code blocks much - code tends to not be that wide - and it means that using a code block for effect doesn't require people to manually wrap strings. Having tried it both ways, this feels more human. Dumbnailing is not a _great_ solution to dealing with huge images, but it's the best we can do at rendering time. A more complete solution would require generating images at multiple sizes.
* Merge branch 'prop/outbox-message-ui'Owen Jacobson2025-05-08
|\
| * Rather than exploding a user into properties inside `runs`, use a helper method.Owen Jacobson2025-05-08
| |
| * Render messages as ghosts when there's a pending delete, too.Owen Jacobson2025-05-06
| |
| * Render "ghost" messages for unsent messages.Owen Jacobson2025-05-06
| | | | | | | | | | | | | | | | | | | | | | There is a subtle race conditon in this code, which is likely not fixable without a protocol change: * Ghost messages can disappear before their "real" message replacement shows up, if the client finishes sending (i.e., receives an HTTP response on the POST) before the server delivers the real message. * Ghost messages can be duplicated briefly, if the client receives the real message before the client finishes sending. Both happen in practice; we make no ordering guarantees between requests. To aviod this, we'd to give clients a way to correlate pending sends with received messages. This would require fundamentally the same capabilities, like per-operation nonces, that preventing duplicate operations will require.
| * Un-nest `Message` from `MessageRun`.Owen Jacobson2025-05-06
| | | | | | | | | | | | A `MessageRun` is a visual container with a specific layout - bordered, with a drop shadow, with a name badge on the top-left, which is either positioned to the left (`other-message`) or right (`own-message`). It is content-agnostic. This facilitates putting things besides live messages inside of a message run. As a side effect, this gets rid of ActiveChannel; most of what it was doing makes more sense living in the channel view's `+page.svelte`.
| * Use a more targetted selector to control link text colours inside of messages.Owen Jacobson2025-05-06
| | | | | | | | Using a wildcard selector here makes this rule surprisingly hard to override, which will be a problem for styling unsent messages.
| * Don't retry operations where we received an unacceptable response.Owen Jacobson2025-05-05
| | | | | | | | | | | | | | | | This was actually two issues in one! Issue 1: `isRetryable` did not consider whether we got a response or not. It assumed that the presence of a request in the error signaled that the error was definitely due to network issues, when in fact it's the presence of a request _and_ the absence of a response. That's my misreading of the Axios docs; the replacement `isRetryable` is more thorough. Issue 2: operations in the outbox queue that fail with an exception stop the outbox drain process from making further progress, _and_ they stay in the queue. The outbox now dequeues jobs that throw an exception, and restarts itself if it terminates with a non-empty queue. The code that does this is _heinous_, but it seems to work well enough… Words I'm sure I won't come to regret.
| * Use the outbox for more than just message sends.Owen Jacobson2025-05-05
| | | | | | | | | | | | | | | | | | | | A handful of operations are "synchronized" - that is, the server sends back information about them when the client asks to perform them, but notifies _all_ clients of completion through the event stream. As of this writing, these operations include sending and deleting messages, creating and deleting channels, and anything that creates new users. We can use the outbox for most of these. I've opted _not_ to use the outbox for creating users, as that often takes place when the client is not connected to the event stream (and can't be) and so cannot discover that the operation has been completed after it is sent. Outboxed tasks are objects, not closures, even though they behave in closure-like ways (`send()` carries out what amounts to "attempt this operation until it succeeds"). This is deliberate; I want the properties of incomplete tasks to be inspectable down the line, so that we can put them in the UI. If they're mere closures, we can't do that. It is deliberate that `outbox.postToChannel` et al do not return promises. I contemplated it, but it will interact weirdly with outbox serialization, when we get to that. Rather than relying on a promise to determine when an operation has completed, clients of `Outbox` should monitor its properties. (We can add additional view properties to make that easier.)
| * Warn the user before navigating away, when the outbox has messages in it.Owen Jacobson2025-05-05
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This is in lieu of saving the outbox. I tried that, and: * If messages are dropped from the saved outbox before calling `api.postToChannel`, then messages "in flight" are lost when the page is reloaded unless the send succeeds after the client vanishes, as they are not re-sent when the page loads. * If messages are dropped from the saved outbox after calling `api.postToChannel`, then messages "in flight" are duplicated when the page is reloaded and they get re-sent. The CAP theorem is real and can hurt you. The appropriate compensating mechanism would be a client-generated per-operation nonce, with server-side support for replaying responses by nonce if an operation already completed. That's a pretty big undertaking, and it's one we should probably do, but it's larger than I want to take on right now. Instead, we warn the user, and they can make their own decision. Except we don't, sometimes. When the client runs in a browser, this event handler prompts the user for confirmation before reloading, navigating away, closing the tab, or quitting. When run in a Safari app container, though, it only warns before reloading. Closing the window or quitting the app do not provoke a prompt. The warning is "best" effort. The failure mode is lost messages, which isn't particularly best.
| * Send messages through an outbox, rather than sending them to the API ↵Owen Jacobson2025-05-02
| | | | | | | | | | | | | | | | directly from the UI. This primarily serves to free up the message input immediately, so that the user can start drafting their next message right away. The wait while a message is being sent is actively disruptive when using Pilcrow on a server with noticable latency (hi.grimoire.ca has around 700ms), and this largely alleviates it. Unsent messages can be lost if the client is closed or deactivated before they make it to the head of the queue.
* | Split up link colours and use fewer wildcard patterns to re-style them.Owen Jacobson2025-05-06
| | | | | | | | | | | | This was causing problems with message colouring, as these rules had specificity similar to constructs like `.message.deleted`. This approach removes their browser default styles, then adds styling specific to the channel sidebar, the app bar, and the message view.
* | Don't try to scroll the last message into view when there is no last message.Owen Jacobson2025-05-05
|/ | | | | | | | This was generating a DOM-related error viewing any empty channel: TypeError: null is not an object (evaluating 'document.querySelector('.message-run:last-child .message:last-child').scrollIntoView') Harmless in practice, but easily fixed and it keeps the console from filling up with natter.
* Retry boot every five seconds.Owen Jacobson2025-04-23
|\ | | | | | | Merged in spite of misgivings. This method will loop over the request until it completes, even if the user moves to a view where the response is no longer relevant.