Barazo default frontend barazo.forum
2
fork

Configure Feed

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

fix(web): reduce thread line width and remove double indentation

ThreadLine width now matches the indent step (22/16/8px) instead of
44px. Remove the separate marginLeft wrapper div -- ancestor lines
themselves provide all indentation. Fixes excessive horizontal space
consumption at deeper nesting levels.

+46 -51
+9 -5
src/components/ancestor-lines.test.tsx
··· 10 10 { uri: 'a1', authorName: 'Alice', replyCount: 3, expanded: true }, 11 11 { uri: 'a2', authorName: 'Bob', replyCount: 5, expanded: true }, 12 12 ] 13 - render(<AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} />) 13 + render( 14 + <AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} lineWidth={22} /> 15 + ) 14 16 const buttons = screen.getAllByRole('button') 15 17 expect(buttons).toHaveLength(2) 16 18 }) ··· 22 24 { uri: 'a3', authorName: 'Carol', replyCount: 1, expanded: true }, 23 25 ] 24 26 const { container } = render( 25 - <AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} /> 27 + <AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} lineWidth={22} /> 26 28 ) 27 29 const lines = container.querySelectorAll('[aria-hidden="true"]') 28 30 expect(lines.length).toBeGreaterThan(0) ··· 39 41 { uri: 'a1', authorName: 'Alice', replyCount: 3, expanded: true }, 40 42 { uri: 'a2', authorName: 'Bob', replyCount: 5, expanded: true }, 41 43 ] 42 - render(<AncestorLines ancestors={ancestors} onToggle={onToggle} showChevron={false} />) 44 + render( 45 + <AncestorLines ancestors={ancestors} onToggle={onToggle} showChevron={false} lineWidth={22} /> 46 + ) 43 47 const buttons = screen.getAllByRole('button') 44 48 await user.click(buttons[0]!) 45 49 expect(onToggle).toHaveBeenCalledWith('a1') ··· 47 51 48 52 it('renders nothing when ancestors array is empty', () => { 49 53 const { container } = render( 50 - <AncestorLines ancestors={[]} onToggle={vi.fn()} showChevron={false} /> 54 + <AncestorLines ancestors={[]} onToggle={vi.fn()} showChevron={false} lineWidth={22} /> 51 55 ) 52 56 expect(container.querySelector('button')).not.toBeInTheDocument() 53 57 }) ··· 55 59 it('passes axe accessibility check', async () => { 56 60 const ancestors = [{ uri: 'a1', authorName: 'Alice', replyCount: 3, expanded: true }] 57 61 const { container } = render( 58 - <AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} /> 62 + <AncestorLines ancestors={ancestors} onToggle={vi.fn()} showChevron={false} lineWidth={22} /> 59 63 ) 60 64 const results = await axe(container) 61 65 expect(results).toHaveNoViolations()
+4 -1
src/components/ancestor-lines.tsx
··· 20 20 ancestors: AncestorInfo[] 21 21 onToggle: (uri: string) => void 22 22 showChevron: boolean 23 + /** Width per line in pixels. Should match indent step. */ 24 + lineWidth: number 23 25 } 24 26 25 - export function AncestorLines({ ancestors, onToggle }: AncestorLinesProps) { 27 + export function AncestorLines({ ancestors, onToggle, lineWidth }: AncestorLinesProps) { 26 28 if (ancestors.length === 0) return null 27 29 28 30 return ( ··· 40 42 replyCount={ancestor.replyCount} 41 43 opacity={opacity} 42 44 showChevron={false} 45 + width={lineWidth} 43 46 /> 44 47 ) 45 48 })}
+25 -41
src/components/reply-branch.tsx
··· 12 12 import type { Reply } from '@/lib/api/types' 13 13 import { type ReplyTreeNode, countDescendants } from '@/lib/build-reply-tree' 14 14 import { 15 - VISUAL_INDENT_CAP, 16 15 DEFAULT_EXPANDED_LEVELS, 17 16 AUTO_COLLAPSE_SIBLING_THRESHOLD, 18 17 AUTO_COLLAPSE_SHOW_COUNT, ··· 101 100 const visibleNodes = shouldLimitSiblings ? nodes.slice(0, AUTO_COLLAPSE_SHOW_COUNT) : nodes 102 101 const hiddenSiblingCount = nodes.length - visibleNodes.length 103 102 104 - const atVisualCap = (nodes[0]?.reply.depth ?? 1) >= VISUAL_INDENT_CAP 105 - 106 103 return ( 107 104 <ol className="list-none space-y-3 pl-0"> 108 105 {visibleNodes.map((node) => { ··· 133 130 return ( 134 131 <li key={node.reply.uri} aria-level={node.reply.depth}> 135 132 {needsBadge && parentHandle && parentPostNumber > 0 && ( 136 - <div style={{ marginLeft: ancestors.length * 44 }}> 133 + <div style={{ marginLeft: ancestors.length * indentStep }}> 137 134 <ReplyToBadge authorHandle={parentHandle} parentPostNumber={parentPostNumber} /> 138 135 </div> 139 136 )} 140 137 <div className="flex gap-0"> 141 - <AncestorLines ancestors={ancestors} onToggle={handleToggle} showChevron={false} /> 138 + <AncestorLines 139 + ancestors={ancestors} 140 + onToggle={handleToggle} 141 + showChevron={false} 142 + lineWidth={indentStep} 143 + /> 142 144 {hasChildren && ( 143 145 <ThreadLine 144 146 expanded={!isCollapsed} ··· 147 149 replyCount={descendantCount} 148 150 opacity={1} 149 151 showChevron={showChevron} 152 + width={indentStep} 150 153 /> 151 154 )} 152 155 <div className="min-w-0 flex-1"> ··· 165 168 type="button" 166 169 onClick={() => toggleCollapse(node.reply.uri)} 167 170 className="mt-1 flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground" 168 - style={{ marginLeft: (ancestors.length + 1) * 44 }} 171 + style={{ marginLeft: (ancestors.length + 1) * indentStep }} 169 172 aria-live="polite" 170 173 > 171 174 {descendantCount} {descendantCount === 1 ? 'reply' : 'replies'} 172 175 </button> 173 176 )} 174 - {hasChildren && 175 - !isCollapsed && 176 - (atVisualCap ? ( 177 - <ReplyBranch 178 - nodes={node.children} 179 - postNumberMap={postNumberMap} 180 - topicUri={topicUri} 181 - allReplies={allReplies} 182 - indentStep={indentStep} 183 - showChevron={showChevron} 184 - ancestors={childAncestors} 185 - onToggleAncestor={handleToggle} 186 - treeParentUri={node.reply.uri} 187 - onReply={onReply} 188 - onDeleteReply={onDeleteReply} 189 - currentUserDid={currentUserDid} 190 - /> 191 - ) : ( 192 - <div style={{ marginLeft: indentStep }}> 193 - <ReplyBranch 194 - nodes={node.children} 195 - postNumberMap={postNumberMap} 196 - topicUri={topicUri} 197 - allReplies={allReplies} 198 - indentStep={indentStep} 199 - showChevron={showChevron} 200 - ancestors={childAncestors} 201 - onToggleAncestor={handleToggle} 202 - treeParentUri={node.reply.uri} 203 - onReply={onReply} 204 - onDeleteReply={onDeleteReply} 205 - currentUserDid={currentUserDid} 206 - /> 207 - </div> 208 - ))} 177 + {hasChildren && !isCollapsed && ( 178 + <ReplyBranch 179 + nodes={node.children} 180 + postNumberMap={postNumberMap} 181 + topicUri={topicUri} 182 + allReplies={allReplies} 183 + indentStep={indentStep} 184 + showChevron={showChevron} 185 + ancestors={childAncestors} 186 + onToggleAncestor={handleToggle} 187 + treeParentUri={node.reply.uri} 188 + onReply={onReply} 189 + onDeleteReply={onDeleteReply} 190 + currentUserDid={currentUserDid} 191 + /> 192 + )} 209 193 </li> 210 194 ) 211 195 })}
+3 -3
src/components/thread-line.test.tsx
··· 61 61 expect(line!.getAttribute('style')).toContain('opacity') 62 62 }) 63 63 64 - it('has adequate tap target (min 44px)', () => { 65 - const { container } = render(<ThreadLine {...defaultProps} />) 64 + it('applies width from prop', () => { 65 + const { container } = render(<ThreadLine {...defaultProps} width={22} />) 66 66 const button = container.querySelector('button')! 67 - expect(button.className).toMatch(/min-w-\[44px\]/) 67 + expect(button.style.width).toBe('22px') 68 68 }) 69 69 70 70 it('passes axe accessibility check', async () => {
+5 -1
src/components/thread-line.tsx
··· 14 14 replyCount: number 15 15 opacity?: number 16 16 showChevron?: boolean 17 + /** Width in pixels. Matches the indent step so lines ARE the indentation. */ 18 + width?: number 17 19 } 18 20 19 21 export function ThreadLine({ ··· 23 25 replyCount, 24 26 opacity = 1, 25 27 showChevron = true, 28 + width = 22, 26 29 }: ThreadLineProps) { 27 30 const label = expanded 28 31 ? `Collapse thread by ${authorName}, ${replyCount} ${replyCount === 1 ? 'reply' : 'replies'}` ··· 35 38 aria-expanded={expanded} 36 39 aria-label={label} 37 40 title={expanded ? 'Collapse thread' : 'Expand thread'} 38 - className="group relative min-w-[44px] shrink-0 cursor-pointer border-none bg-transparent p-0" 41 + className="group relative shrink-0 cursor-pointer border-none bg-transparent p-0" 42 + style={{ width }} 39 43 > 40 44 {showChevron && ( 41 45 <span className="absolute left-1/2 top-1 -translate-x-1/2 text-border transition-colors group-hover:text-accent-foreground">