BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: image rendering/overlay positioning

* added modal inventory doc

+80 -20
+39
docs/code-quality.md
··· 1 + --- 2 + title: Code Quality Audits 3 + updated: 2026-04-15 4 + --- 5 + 6 + There is a ton of repeated code in this project. This document tracks repeated patterns 7 + for future refactoring. 8 + 9 + ## Dialogs 10 + 11 + ### Inventory (2026-04-15) 12 + 13 + | Component | File (relative to `src/components/`) | Overlay style | Dialog semantics | Notes | 14 + | --------------------------- | ---------------------------------------------- | ---------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------- | 15 + | Image gallery overlay | `/feeds/ImageGallery.tsx` | Fullscreen glass overlay, centered media stage | `role="dialog"` + `aria-modal="true"` | Handles image navigation/download + keyboard controls. | 16 + | Report dialog | `/moderation/ReportDialog.tsx` | Centered card on fullscreen scrim | Missing `role="dialog"`/`aria-modal` | Uses shared backdrop/surface naming but local implementation. | 17 + | Settings confirmation modal | `/settings/SettingsPanel.tsx` | Centered confirmation card | Missing `role="dialog"`/`aria-modal` | Inline modal implementation nested inside settings panel. | 18 + | Add column panel | `/deck/AddColumnPanel.tsx` | Right drawer with backdrop | `role="dialog"` + `aria-modal="true"` | Drawer-style overlay, good semantic baseline. | 19 + | Profile actor list overlay | `/profile/ProfileActorList.tsx` | Bottom sheet style overlay | `role="dialog"` + `aria-modal="true"` | Focus target is set on open; closes on scrim click and `Escape`. | 20 + | Follow hygiene panel | `/profile/FollowHygienePanel.tsx` | Right sheet/panel | `role="dialog"` + `aria-modal="true"` | Large panel overlay with keyboard and progress handling. | 21 + | Follow hygiene confirmation | `/profile/FollowHygeineConfirmationDialog.tsx` | Centered danger confirmation card | Missing `role="dialog"`/`aria-modal` | Standalone confirmation overlay implementation. | 22 + | Feed composer | `/feeds/FeedComposer.tsx` | Fullscreen composer dialog overlay | Missing `role="dialog"`/`aria-modal` | Uses custom panel and scrim; keyboard handling managed outside. | 23 + | Drafts list overlay | `/feeds/DraftsList.tsx` | Bottom-aligned panel over fullscreen scrim | Missing `role="dialog"`/`aria-modal` | Similar layering/animation style to composer. | 24 + | Thread drawer | `/posts/ThreadDrawer.tsx` | Right drawer over fullscreen scrim | Missing `role="dialog"`/`aria-modal` | Uses close button + escape handler, but no dialog semantics yet. | 25 + | Context menu (non-dialog) | `/shared/ContextMenu.tsx` | Fullscreen hit target + positioned menu | `role="menu"` | Not a modal dialog; still part of overlay infrastructure. | 26 + 27 + ### Repeated Patterns 28 + 29 + - Scrim/backdrop classes are duplicated across multiple files with slight class/token variations. 30 + - Dialog/card enter-exit animation values are redefined in each component. 31 + - Escape-key listener setup/teardown appears in several overlays and drawers. 32 + - Many overlays still rely on click-to-close behavior without consistent semantic attributes. 33 + 34 + ### Refactor Candidates 35 + 36 + - Extract a shared `OverlayBackdrop` primitive for fullscreen scrim + portal + optional click-to-close. 37 + - Extract a shared `DialogSurface` primitive for animated card/sheet containers. 38 + - Standardize dialog semantics (`role`, `aria-modal`, labeling) across all modal-like overlays. 39 + - Centralize escape-key wiring in an overlay utility hook to reduce per-component lifecycle code.
+31 -20
src/components/feeds/ImageGallery.tsx
··· 30 30 function GalleryOverlay(props: GalleryOverlayProps) { 31 31 return ( 32 32 <Motion.div 33 - class="fixed inset-0 z-60 grid min-h-0 grid-rows-[1fr_auto] bg-surface-container-highest/70 p-4 backdrop-blur-[20px] max-[760px]:p-3" 33 + role="dialog" 34 + aria-modal="true" 35 + aria-label="Image gallery" 36 + class="fixed inset-0 z-60 overflow-hidden bg-surface-container-highest/70 p-4 backdrop-blur-[20px] max-[760px]:p-3" 34 37 initial={{ opacity: 0 }} 35 38 animate={{ opacity: 1 }} 36 39 exit={{ opacity: 0 }} ··· 41 44 class="absolute inset-0 border-0 bg-transparent" 42 45 onClick={() => props.onClose()} /> 43 46 44 - <div class="relative z-1 grid min-h-0"> 45 - <Toolbar 46 - current={props.hasManyImages ? props.index + 1 : 1} 47 - disabled={props.downloadPending} 48 - total={props.hasManyImages ? props.imageCount : 1} 49 - onDownload={props.onDownload} 50 - onClose={props.onClose} 51 - pending={props.downloadPending} /> 47 + <div class="pointer-events-none absolute inset-x-4 top-4 z-2 max-[760px]:inset-x-3 max-[760px]:top-3"> 48 + <div class="pointer-events-auto"> 49 + <Toolbar 50 + current={props.hasManyImages ? props.index + 1 : 1} 51 + disabled={props.downloadPending} 52 + total={props.hasManyImages ? props.imageCount : 1} 53 + onDownload={props.onDownload} 54 + onClose={props.onClose} 55 + pending={props.downloadPending} /> 56 + </div> 57 + </div> 52 58 53 - <div class="relative grid min-h-0 place-items-center px-14 py-3 max-[760px]:px-11"> 59 + <div class="relative z-1 h-full min-h-0 w-full px-14 py-3 max-[760px]:px-11"> 60 + <div class="relative mx-auto flex h-full w-full max-w-[min(96rem,100%)] items-center justify-center"> 54 61 <img 55 62 class="max-h-full max-w-full rounded-2xl object-contain shadow-[0_30px_60px_rgba(0,0,0,0.35)]" 56 63 src={props.selectedImage?.fullsize ?? props.selectedImage?.thumb} ··· 61 68 </div> 62 69 </div> 63 70 64 - <CaptionPanel 65 - alt={props.selectedImage?.alt} 66 - authorHandle={props.authorHandle} 67 - authorHref={props.authorHref} 68 - expanded={props.expanded} 69 - postText={props.postText} 70 - showToggle={props.showPostTextToggle} 71 - onToggleExpand={props.onToggleExpand} /> 71 + <div class="pointer-events-none absolute inset-x-4 bottom-4 z-2 max-[760px]:inset-x-3 max-[760px]:bottom-3"> 72 + <div class="pointer-events-auto"> 73 + <CaptionPanel 74 + alt={props.selectedImage?.alt} 75 + authorHandle={props.authorHandle} 76 + authorHref={props.authorHref} 77 + expanded={props.expanded} 78 + postText={props.postText} 79 + showToggle={props.showPostTextToggle} 80 + onToggleExpand={props.onToggleExpand} /> 81 + </div> 82 + </div> 72 83 </Motion.div> 73 84 ); 74 85 } ··· 84 95 85 96 function Toolbar(props: ToolbarProps) { 86 97 return ( 87 - <div class="flex min-h-10 items-center justify-between gap-3"> 98 + <div class="relative z-1 mx-auto flex min-h-10 w-full max-w-[min(96rem,100%)] items-center justify-between gap-3"> 88 99 <p class="m-0 text-xs uppercase tracking-[0.12em] text-on-surface-variant">{props.current} / {props.total}</p> 89 100 <div class="flex items-center gap-2"> 90 101 <button ··· 138 149 function CaptionPanel(props: CaptionPanelProps) { 139 150 const label = () => props.expanded ? "Show less" : "Show more"; 140 151 return ( 141 - <div class="relative z-1 grid gap-2 rounded-2xl bg-surface-container-high/86 p-4 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]"> 152 + <div class="relative z-1 mx-auto grid w-full max-w-[min(96rem,100%)] gap-2 rounded-2xl bg-surface-container-high/86 p-4 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]"> 142 153 <Show when={props.alt}>{(alt) => <p class="m-0 text-sm leading-normal text-on-surface">{alt()}</p>}</Show> 143 154 <Show when={(props.postText ?? "").trim().length > 0}> 144 155 <div class="grid items-start gap-1">
+10
src/components/feeds/tests/ImageGallery.test.tsx
··· 36 36 expect(onClose).toHaveBeenCalledTimes(1); 37 37 }); 38 38 39 + it("renders a modal gallery dialog container", () => { 40 + render(() => ( 41 + <ImageGallery images={[...GALLERY_IMAGES]} open postText="Post text" startIndex={0} onClose={() => {}} /> 42 + )); 43 + 44 + const dialog = screen.getByRole("dialog", { name: "Image gallery" }); 45 + expect(dialog).toHaveAttribute("aria-modal", "true"); 46 + expect(dialog.className).toContain("fixed inset-0"); 47 + }); 48 + 39 49 it("truncates post copy and toggles expansion", () => { 40 50 render(() => ( 41 51 <ImageGallery images={[...GALLERY_IMAGES]} open postText={"x".repeat(220)} startIndex={0} onClose={() => {}} />