summaryrefslogtreecommitdiff
path: root/ui
Commit message (Collapse)AuthorAge
* Remove remnant `html` class from swatch textinputs.Owen Jacobson2025-07-18
| | | | This was a leftover from the idea that different swatches might have different input notations - and they do, but we turned out not to need to style them differently. And, in any event, this class was applied (only) to inputs that _aren't HTML_, because of 01ed82ac4f89810161fbc3aa1cb8e4691fb8938b.
* Create swatches for Svelte components.ojacobson2025-07-10
|\ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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. They do not require setup or login for simplicity's sake and because they don't _do_ anything that requires either of those things. Swatches are manually curated. First, we lack the technical infrastructure needed to do this based on static analysis, and second, manual curation lets us include affordances like "interesting values," that would be tricky to express as part of the type or schema for the component. The tradeoff, however, is that they will fall out of step with the components if not reviewed regularly. Swatches are _possible_ because we've gone to efforts to avoid global data access or direct side effects (including API requests) in our components, delegating that upwards to `+page`s and `+layout`s. However, the isolation is imperfect. For example, the swatch for `Conversation`, which renders the conversation sidebar entries, causes actual attempts to boot the app as browsers pre-fetch the links on mouseover, and clicking them will take the user to the "real" application because they really are links. Merges swatch into main.
| * Do not support users entering bare HTML in swatches.Owen Jacobson2025-07-09
| | | | | | | | | | | | | | | | You can inject Javascript into a swatch that uses `{@html <expr>}` fairly easily. `<script>foo()</script>` doesn't appear to work, but `<img src="x" onerror="foo()">` does, for example. That code then runs with the same access to cookies, and the same access to local data, as the Pilcrow client. This change removes that capability, by replacing the two swatches that exposed it with more limited examples. I love the generality and flexibility of generic HTML entry here, and I think it might have been useful for swatching components that are generic DOM containers (which both `Message` and `MessageRun` are today), but swatches are a user interface and are exposed to _all_ users. A user who is unfamiliar with HTML and Javascript, but who is persuaded to open a swatch and enter some code into it (think about an attacker who tells their victim "hey check out this funny thing that happens," preying on curiousity, while providing a lightly-obfuscated payload) can then impersonate that user, exfiltrate anything saved locally, or potentially install persistent code using JS' various background-processing APIs. Gnarly stuff. We're not up to mitigating that in place. Anyone who knows JS can likely learn to build the client from source, and can experiment with arbitrary input that way, taking responsibility for the results in the process, while anyone who doesn't is unlikely to be persuaded to set up an entire Node toolchain just for an exploit.
| *-----------------. Implement swatches for the existing component inventory.Owen Jacobson2025-07-08
| |\ \ \ \ \ \ \ \ \ \
| | | | | | | | | | | * Create swatch for the `Message` component.Owen Jacobson2025-07-08
| | | | | | | | | | | |
| | | | | | | | | | * | Create swatch for the `MessageInput` component.Owen Jacobson2025-07-08
| | | | | | | | | | |/
| | | | | | | | | * / Create swatch for the `MessageRun` component.Owen Jacobson2025-07-08
| | | | | | | | | |/
| | | | | | | | * / Create swatch for the `LogOut` component.Owen Jacobson2025-07-08
| | | | | | | | |/
| | | | | | | * / Create swatch for the `LogIn` component.Owen Jacobson2025-07-08
| | | | | | | |/
| | | | | | * / Create swatch for the `Invites` component.Owen Jacobson2025-07-08
| | | | | | |/
| | | | | * / Create swatch for the `Invite` component.Owen Jacobson2025-07-08
| | | | | |/
| | | | * / Create swatch for the `CreateConversationForm` component.Owen Jacobson2025-07-08
| | | | |/
| | | * / Create a swatch for the `ConversationList` component.Owen Jacobson2025-07-08
| | | |/
| | * / Create a swatch for the `Conversation` component.Owen Jacobson2025-07-08
| | |/
| * / Add a swatch for the `ChangePassword` 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 container divs for `MessageInput` and `CreateConversationForm`.ojacobson2025-07-09
|\ \ | | | | | | | | | | | | | | | | | | | | | The styles for the `MessageInput` and `CreateConversationForm` components assumed that a container div would be present, and the components did not render as intended without that div. Therefore: the divs were "part of" the component in most of the ways that matter, not part of the context in which the component is used. It turns out that those divs aren't necessary or interesting anyways - they were targets for layout, but the same layout can be achieved without them. This change removes the divs entirely. Merges component-div-nesting into main.
| * | A few semantically-thin wrapper divs.Owen Jacobson2025-07-08
| | | | | | | | | | | | This is an extension of the previous commit: we don't need these divs _at all_ to achieve the layout we want, and we aren't attaching behaviour or semantics to them, so, out they go.
| * | Move container divs for components into those components.Owen Jacobson2025-07-08
| | | | | | | | | | | | | | | | | | The styles for the `MessageInput` and `CreateConversationForm` components assume that the container div will be present, and the components will not render as intended without them. Therefore: they are "part of" the component in most of the ways that matter, not part of the context in which the component is used. Moving the divs into the component will make it easier to reuse these components (for example, in swatches). The diff for this looks worse than it is because of indentation changes.
* | | Stop sending `{}` to the `/api/auth/login` endpoint when the login form ↵ojacobson2025-07-09
|\ \ \ | |_|/ |/| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | hasn't been touched. Steps to reproduce: **Note**: You will need to watch the traffic in a DOM inspector; this has no user-observable symptoms because there's presently no error reporting for the login form. 1. In a new private tab, visit the `/login` page of a Pilcrow instance. 2. **Without touching the username or password fields**, click `sign in`. The client _should_ send a request to `/api/auth/login` with the following payload: ```json { "name": "", "password": "" } ``` However, it instead sends an empty payload, leading to a 422 Unprocessable Content response as the request is missing required fields. Subsequent requests, or any request after the user enters data in the input fields, are correctly serialized. Merges login-form-nulls into main.
| * | Set non-`undefined` initial values for the login form.Owen Jacobson2025-07-08
| | | | | | | | | | | | The default state of a `$state()` with no arguments is `undefined`, which was then leaking out of this component if the user clicks `sign in` without changing the values. Axiom, our HTTP client library, suppresses fields with `undefined` values in JSON payloads (sensibly enough), leading to empty requests.
| * | Bug: the login form generates incorrect requests (once per pageview).Owen Jacobson2025-07-08
| |/ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Steps to reproduce: **Note**: You will need to watch the traffic in a DOM inspector; this has no user-observable symptoms because there's presently no error reporting for the login form. 1. In a new private tab, visit the `/login` page of a Pilcrow instance. 2. **Without touching the username or password fields**, click `sign in`. The client _should_ send a request to `/api/auth/login` with the following payload: ```json { "name": "", "password": "" } ``` However, it instead sends an empty payload, leading to a 422 Unprocessable Content response as the request is missing required fields. Subsequent requests, or any request after the user enters data in the input fields, are correctly serialized.
* / 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
| |