From 735b8dc7661885e99133c246a557744c493d6ab2 Mon Sep 17 00:00:00 2001 From: Amr Mohammed El-Sheraey <141947355+AmrMsCLL@users.noreply.github.com> Date: Sat, 7 Nov 2026 02:51:24 +0200 Subject: [PATCH 1/5] fix(motion): state pulses stop under prefers-reduced-motion The reconnecting dot, an armed confirm button, the saving indicator, the stage's live dot and the editor's connection dot looped state-pulse regardless of the user's motion preference. The pulses now live in one no-preference block beside the keyframes; the colour still shows the state. --- site/app/styles/graphite.css | 16 ++++++++++++++-- site/app/styles/pages/editor.css | 2 -- site/app/styles/pages/signed-in.css | 2 -- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/site/app/styles/graphite.css b/site/app/styles/graphite.css index f1db9b3..9d07876 100644 --- a/site/app/styles/graphite.css +++ b/site/app/styles/graphite.css @@ -97,7 +97,6 @@ } .live-pulse[data-state='reconnecting'] i { background: var(--color-wip); - animation: state-pulse 1.2s ease-in-out infinite; } .badge { display: inline-flex; @@ -601,7 +600,20 @@ textarea.field { .chip-button[data-armed] { color: var(--color-danger); border-color: var(--color-danger); - animation: state-pulse 1.2s ease-in-out infinite; +} +@media (prefers-reduced-motion: no-preference) { + .live-pulse[data-state='reconnecting'] i, + .chip-button[data-armed] { + animation: state-pulse 1.2s ease-in-out infinite; + } + .setting-state[data-state='saving'] i, + .editor .facts .live[data-connection='connecting'] i { + animation: state-pulse 0.9s ease-in-out infinite; + } + .stage > .bar .live::before, + .editor .facts .live[data-connection='online'] i { + animation: state-pulse 2s ease-in-out infinite; + } } @keyframes state-pulse { 50% { diff --git a/site/app/styles/pages/editor.css b/site/app/styles/pages/editor.css index 964bb6a..bbc220c 100644 --- a/site/app/styles/pages/editor.css +++ b/site/app/styles/pages/editor.css @@ -45,11 +45,9 @@ } .editor .facts .live[data-connection='online'] i { background: var(--color-live); - animation: state-pulse 2s ease-in-out infinite; } .editor .facts .live[data-connection='connecting'] i { background: var(--color-wip); - animation: state-pulse 0.9s ease-in-out infinite; } .editor .facts .live[data-connection='offline'] i { background: var(--color-wip); diff --git a/site/app/styles/pages/signed-in.css b/site/app/styles/pages/signed-in.css index de61f42..515bcab 100644 --- a/site/app/styles/pages/signed-in.css +++ b/site/app/styles/pages/signed-in.css @@ -108,7 +108,6 @@ } .setting-state[data-state='saving'] i { background: var(--color-wip); - animation: state-pulse 0.9s ease-in-out infinite; } .setting-state[data-state='saved'] { color: var(--color-live); @@ -712,7 +711,6 @@ height: 6px; border-radius: 50%; background: var(--color-live); - animation: state-pulse 2s ease-in-out infinite; } .stage-body { padding: 0.5rem 2rem 2.5rem; From f8c634c0fec27745accaf0ea4d7b5f5ec58667a9 Mon Sep 17 00:00:00 2001 From: Amr Mohammed El-Sheraey <141947355+AmrMsCLL@users.noreply.github.com> Date: Sat, 7 Nov 2026 02:51:25 +0200 Subject: [PATCH 2/5] fix(editor): the post editor works from the keyboard The formatting toolbar is one tab stop with arrow, Home and End keys moving between its controls. The cover upload is a button, so it is reachable without a pointer. The tags field keeps the text being typed, so a comma can be entered; it used to re-render without it on every keystroke. The post page's heading list no longer carries Tailwind's outline utility by accident. --- site/app/editor/PostEditor.tsx | 290 +++++++++++++++++++------------- site/app/routes/post-editor.tsx | 44 +++-- site/app/routes/post.tsx | 2 +- site/app/styles/pages/blog.css | 2 +- 4 files changed, 200 insertions(+), 138 deletions(-) diff --git a/site/app/editor/PostEditor.tsx b/site/app/editor/PostEditor.tsx index 0f4c4a3..aa95fe1 100644 --- a/site/app/editor/PostEditor.tsx +++ b/site/app/editor/PostEditor.tsx @@ -4,7 +4,16 @@ import Collaboration from '@tiptap/extension-collaboration'; import { CollaborationCaret } from '@tiptap/extension-collaboration-caret'; import { Placeholder } from '@tiptap/extensions'; import { EditorContent, useEditor, useEditorState } from '@tiptap/react'; -import { useEffect, useState, type FormEvent } from 'react'; +import { + createContext, + useContext, + useEffect, + useRef, + useState, + type FocusEvent, + type FormEvent, + type KeyboardEvent, +} from 'react'; import { readingMinutes } from '../posts'; import { CALLOUT_TINTS, columnsOf, postExtensions } from './extensions'; import { holdSession, openSession } from './session'; @@ -52,6 +61,8 @@ function selection(user: Record) { type Ask = { kind: 'link' | 'video'; value: string } | null; +const StopContext = createContext<(label: string) => 0 | -1>(() => 0); + function Mark({ on, label, @@ -65,6 +76,7 @@ function Mark({ act: () => void; className?: string; }) { + const stop = useContext(StopContext); return ( + { + const file = event.target.files?.[0]; + if (file) { + void uploadCover(file); + } + event.target.value = ''; + }} + /> {draft.coverUpload && (