ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

add toggle and dropdown components

byarielm.fyi e7b24485 6cd4d622

verified
Changed files
+783 -2
dist
docs
src
+2 -2
dist/index.html
··· 26 26 content="black-translucent" 27 27 /> 28 28 <title>ATLast: Find Your People in the ATmosphere</title> 29 - <script type="module" crossorigin src="/assets/index-DCjXuvAz.js"></script> 30 - <link rel="stylesheet" crossorigin href="/assets/index-BxAsnHb8.css"> 29 + <script type="module" crossorigin src="/assets/index-CdDButDP.js"></script> 30 + <link rel="stylesheet" crossorigin href="/assets/index-F6FWqyRt.css"> 31 31 </head> 32 32 <body> 33 33 <div id="root"></div>
+8
docs/git-history.json
··· 1 1 [ 2 2 { 3 + "hash": "6cd4d622930e2a43531f2df40d930ceb5d2b4dbc", 4 + "short_hash": "6cd4d62", 5 + "author": "Ariel M. Lighty", 6 + "date": "2025-12-24T00:27:50-05:00", 7 + "message": "use shared card component for history and results", 8 + "files_changed": 4 9 + }, 10 + { 3 11 "hash": "cc586d28ea8d4544467392be1083fdec11731814", 4 12 "short_hash": "cc586d2", 5 13 "author": "Ariel M. Lighty",
+561
docs/graph-data.json
··· 1539 1539 "created_at": "2025-12-23T21:11:21.224146800-05:00", 1540 1540 "updated_at": "2025-12-23T21:11:21.224146800-05:00", 1541 1541 "metadata_json": "{\"branch\":\"master\",\"commit\":\"cc586d2\",\"confidence\":95}" 1542 + }, 1543 + { 1544 + "id": 141, 1545 + "change_id": "954f8b75-05eb-44de-afd6-89c349587b2e", 1546 + "node_type": "observation", 1547 + "title": "All bug fixes complete and committed separately: 5 commits (CLAUDE.md update, login typeahead/@ stripping, card overlap fix, mobile alignment, toast refactor). Build passes. Ready for testing.", 1548 + "description": null, 1549 + "status": "pending", 1550 + "created_at": "2025-12-23T21:11:46.224288400-05:00", 1551 + "updated_at": "2025-12-23T21:11:46.224288400-05:00", 1552 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1553 + }, 1554 + { 1555 + "id": 142, 1556 + "change_id": "6b5a1e7d-27d5-4a7a-9eba-8c0495987ba6", 1557 + "node_type": "action", 1558 + "title": "Re-fixing VirtualizedResultsList spacing - fixed gap between cards", 1559 + "description": null, 1560 + "status": "pending", 1561 + "created_at": "2025-12-23T21:22:44.904557100-05:00", 1562 + "updated_at": "2025-12-23T21:22:44.904557100-05:00", 1563 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 1564 + }, 1565 + { 1566 + "id": 143, 1567 + "change_id": "b6a1f3ef-068a-4ae7-a3a4-130fe830fe4c", 1568 + "node_type": "observation", 1569 + "title": "Current implementation uses estimateSize to guess heights (80px base + 140px per match). Virtualizer positions cards based on estimates via translateY. When actual height != estimate, cards overlap. pb-4 gap is inside card, not between virtual items. Need dynamic measurement with measureElement.", 1570 + "description": null, 1571 + "status": "pending", 1572 + "created_at": "2025-12-23T21:23:22.876915400-05:00", 1573 + "updated_at": "2025-12-23T21:23:22.876915400-05:00", 1574 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1575 + }, 1576 + { 1577 + "id": 144, 1578 + "change_id": "37bbbbd0-0fdd-4ce3-aa86-dd88d45513fc", 1579 + "node_type": "outcome", 1580 + "title": "Successfully refactored VirtualizedResultsList to use dynamic measurement. Replaced complex estimateSize (40 lines) with simple 200px estimate + measureElement callback. Added data-index and ref={virtualizer.measureElement}. Virtualizer now measures actual rendered heights and positions cards correctly with fixed pb-4 gap. Build passes.", 1581 + "description": null, 1582 + "status": "pending", 1583 + "created_at": "2025-12-23T21:24:18.072543400-05:00", 1584 + "updated_at": "2025-12-23T21:24:18.072543400-05:00", 1585 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1586 + }, 1587 + { 1588 + "id": 145, 1589 + "change_id": "e8b327a0-372f-478b-a33a-2e1ce554bed2", 1590 + "node_type": "action", 1591 + "title": "Analyzing badge alignment pattern in HistoryTab vs SearchResultCard", 1592 + "description": null, 1593 + "status": "pending", 1594 + "created_at": "2025-12-23T21:46:16.661717400-05:00", 1595 + "updated_at": "2025-12-23T21:46:16.661717400-05:00", 1596 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 1597 + }, 1598 + { 1599 + "id": 146, 1600 + "change_id": "9eae410e-0b10-442f-8291-6f54b9e2f900", 1601 + "node_type": "observation", 1602 + "title": "HistoryTab pattern (line 135): 'sm:ml-0 -ml-14' = small screens shift left 56px to align with avatar, medium+ no shift to align with Platform+Link content. SearchResultCard (lines 65, 77): '-ml-4 pl-0 sm:pl-[60px]' = small screens at -4px (wrong, shifts too far left), medium+ at 56px from card edge but should be 72px to align with Name/Handle. The -ml-4 causes misalignment. Standard pattern should be responsive padding: pl-0 on small (align with avatar), sm:pl-[60px] on medium+ (align with Name/Handle).", 1603 + "description": null, 1604 + "status": "pending", 1605 + "created_at": "2025-12-23T21:48:58.458041400-05:00", 1606 + "updated_at": "2025-12-23T21:48:58.458041400-05:00", 1607 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1608 + }, 1609 + { 1610 + "id": 147, 1611 + "change_id": "31952cd1-2b20-4df9-b99e-2a8fecd45845", 1612 + "node_type": "outcome", 1613 + "title": "Fixed badge alignment in SearchResultCard. Removed -ml-4 negative margin that shifted badges 4px left of card edge on mobile. Changed sm:pl-[60px] to md:pl-[60px] for medium breakpoint. Pattern now matches HistoryTab: mobile aligns with avatar (pl-0), medium+ indents to align with Name/Handle (md:pl-[60px]). Fixed both badges (line 65) and description (line 77). Build passes.", 1614 + "description": null, 1615 + "status": "pending", 1616 + "created_at": "2025-12-23T21:50:51.492205100-05:00", 1617 + "updated_at": "2025-12-23T21:50:51.492205100-05:00", 1618 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1619 + }, 1620 + { 1621 + "id": 148, 1622 + "change_id": "adec9fe9-7462-4734-9b87-ab30558da687", 1623 + "node_type": "action", 1624 + "title": "Applying SearchResultCard spacing pattern to HistoryTab upload cards", 1625 + "description": null, 1626 + "status": "pending", 1627 + "created_at": "2025-12-23T21:59:29.095361600-05:00", 1628 + "updated_at": "2025-12-23T21:59:29.095361600-05:00", 1629 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 1630 + }, 1631 + { 1632 + "id": 149, 1633 + "change_id": "6b36b07d-75af-49a7-a095-7ff93ef9f4db", 1634 + "node_type": "outcome", 1635 + "title": "Applied consistent spacing pattern to HistoryTab upload cards. Changed badges from 'py-1.5 sm:ml-0 -ml-14' to 'pl-0 sm:pl-[56px]' to match SearchResultCard pattern. Badge alignment: mobile aligns with avatar edge (pl-0), medium+ indents to align with Platform/Link content (sm:pl-[56px] = 40px avatar + 16px gap). Build passes.", 1636 + "description": null, 1637 + "status": "pending", 1638 + "created_at": "2025-12-23T22:02:18.638925800-05:00", 1639 + "updated_at": "2025-12-23T22:02:18.638925800-05:00", 1640 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1641 + }, 1642 + { 1643 + "id": 150, 1644 + "change_id": "a11f624c-93ae-49e7-81e1-d8d68ac9eca8", 1645 + "node_type": "action", 1646 + "title": "Analyzing SearchResultCard structure: wrapper div with p-3, top flex row with gap-3, badges as sibling row with pl-0 sm:pl-[44px]. HistoryTab currently has different structure - Card IS the flex container with badges INSIDE flex-1. Need to restructure to match pattern.", 1647 + "description": null, 1648 + "status": "pending", 1649 + "created_at": "2025-12-23T22:06:39.534009400-05:00", 1650 + "updated_at": "2025-12-23T22:06:39.534009400-05:00", 1651 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1652 + }, 1653 + { 1654 + "id": 151, 1655 + "change_id": "87d57666-91a4-45c9-a3f1-628dcc395db3", 1656 + "node_type": "outcome", 1657 + "title": "Restructured HistoryTab to match SearchResultCard pattern. Moved p-4 from Card to wrapper div. Created top flex row (flex gap-4 mb-1) with avatar + content. Moved badges out of flex-1 to be sibling row with pl-0 sm:pl-[56px]. Now badges align with avatar on mobile (pl-0) and indent to match Platform name on medium+ (56px = 40px avatar + 16px gap). Build passes.", 1658 + "description": null, 1659 + "status": "pending", 1660 + "created_at": "2025-12-23T22:08:28.819634300-05:00", 1661 + "updated_at": "2025-12-23T22:08:28.819634300-05:00", 1662 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1663 + }, 1664 + { 1665 + "id": 152, 1666 + "change_id": "eb1b6810-9528-4e4e-b35e-43a02ccf8f3c", 1667 + "node_type": "action", 1668 + "title": "Analyzing shared component feasibility for card structure pattern", 1669 + "description": null, 1670 + "status": "pending", 1671 + "created_at": "2025-12-23T22:28:44.491047100-05:00", 1672 + "updated_at": "2025-12-23T22:28:44.491047100-05:00", 1673 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 1674 + }, 1675 + { 1676 + "id": 153, 1677 + "change_id": "0099b286-7090-459e-8fc6-1abccf3488bb", 1678 + "node_type": "action", 1679 + "title": "Creating shared CardItem component with composition pattern: avatar/content/action/badges/description slots, configurable padding and responsive badge indent", 1680 + "description": null, 1681 + "status": "pending", 1682 + "created_at": "2025-12-24T00:20:16.938290100-05:00", 1683 + "updated_at": "2025-12-24T00:20:16.938290100-05:00", 1684 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1685 + }, 1686 + { 1687 + "id": 154, 1688 + "change_id": "41384be8-f128-4424-9419-2626fc7ac8f4", 1689 + "node_type": "outcome", 1690 + "title": "Successfully created CardItem component and refactored both SearchResultCard and HistoryTab to use it. Extracted 60+ lines of duplicate structure into reusable component. CardItem provides: avatar/content/action/badges/description slots, configurable padding (p-3/p-4), responsive badge indent (sm:pl-[44px]/sm:pl-[56px]). Code now follows composition pattern with single source of truth for layout. Build passes, bundle size unchanged (284.39 KB).", 1691 + "description": null, 1692 + "status": "pending", 1693 + "created_at": "2025-12-24T00:22:11.342098400-05:00", 1694 + "updated_at": "2025-12-24T00:22:11.342098400-05:00", 1695 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1696 + }, 1697 + { 1698 + "id": 155, 1699 + "change_id": "76e2700c-0324-4563-81d1-b31dc3e85460", 1700 + "node_type": "goal", 1701 + "title": "Refactor UI components: create shared components for wizard/upload/settings, fix progress bar", 1702 + "description": null, 1703 + "status": "pending", 1704 + "created_at": "2025-12-24T00:58:08.581579500-05:00", 1705 + "updated_at": "2025-12-24T00:58:08.581579500-05:00", 1706 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"Follow React+Tailwild best practices, use DRY principles. Make shared components for repeated patterns across setup wizard and upload / settings tabs. Prefer the settings toggle to checkboxes. Keep the upload vs. which platform colors as-is. Make drop-downs a separate component too, and describe options for using .ico files from platform in the drop-down next to the text similar to the history card social link. Re-implement progress bar on the setup wizard, it was broken by commit f3c536d1.\"}" 1707 + }, 1708 + { 1709 + "id": 156, 1710 + "change_id": "28f2b394-61e7-4840-9c33-838a1bcf0e04", 1711 + "node_type": "observation", 1712 + "title": "Progress bar broken: ProgressBar wizard variant missing bg color, wrong className usage in SetupWizard line 103", 1713 + "description": null, 1714 + "status": "pending", 1715 + "created_at": "2025-12-24T00:58:56.442852800-05:00", 1716 + "updated_at": "2025-12-24T00:58:56.442852800-05:00", 1717 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1718 + }, 1719 + { 1720 + "id": 157, 1721 + "change_id": "3b149b2b-0e2d-4e38-a2ed-858836cb2e24", 1722 + "node_type": "observation", 1723 + "title": "Found repeated patterns: Toggle (2x in Settings), Dropdown (4x across files), segmented progress bar (replaced incorrectly)", 1724 + "description": null, 1725 + "status": "pending", 1726 + "created_at": "2025-12-24T00:59:33.374749300-05:00", 1727 + "updated_at": "2025-12-24T00:59:33.374749300-05:00", 1728 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1729 + }, 1730 + { 1731 + "id": 158, 1732 + "change_id": "37ab3ef1-445b-46cc-bf51-25ad24f6c131", 1733 + "node_type": "decision", 1734 + "title": "Choose component architecture: Extract Toggle, Dropdown, FormField + fix ProgressBar vs larger form library", 1735 + "description": null, 1736 + "status": "pending", 1737 + "created_at": "2025-12-24T00:59:55.890141500-05:00", 1738 + "updated_at": "2025-12-24T00:59:55.890141500-05:00", 1739 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1740 + }, 1741 + { 1742 + "id": 159, 1743 + "change_id": "8a6cd252-e3a1-4c68-a50f-05f3574016ed", 1744 + "node_type": "option", 1745 + "title": "Extract individual components - lightweight, follows existing pattern", 1746 + "description": null, 1747 + "status": "pending", 1748 + "created_at": "2025-12-24T00:59:57.448827700-05:00", 1749 + "updated_at": "2025-12-24T00:59:57.448827700-05:00", 1750 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1751 + }, 1752 + { 1753 + "id": 160, 1754 + "change_id": "369d275f-5ad7-4d13-aad6-7bcd69ef9e3d", 1755 + "node_type": "outcome", 1756 + "title": "Chose option A: Extract individual shared components. Matches existing component pattern (Card, Badge, etc), no new deps, easier to maintain", 1757 + "description": null, 1758 + "status": "pending", 1759 + "created_at": "2025-12-24T00:59:59.084055-05:00", 1760 + "updated_at": "2025-12-24T00:59:59.084055-05:00", 1761 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1762 + }, 1763 + { 1764 + "id": 161, 1765 + "change_id": "3e38a61f-16f1-4a68-a943-e66b11df29ae", 1766 + "node_type": "action", 1767 + "title": "Creating Toggle component", 1768 + "description": null, 1769 + "status": "pending", 1770 + "created_at": "2025-12-24T01:00:28.980093800-05:00", 1771 + "updated_at": "2025-12-24T01:00:28.980093800-05:00", 1772 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1773 + }, 1774 + { 1775 + "id": 162, 1776 + "change_id": "1be7b49c-5da2-4b2c-bd81-7900fdcf8584", 1777 + "node_type": "outcome", 1778 + "title": "Created Toggle, Dropdown, DropdownWithIcons components. Fixed ProgressBar wizard variant to use segmented display", 1779 + "description": null, 1780 + "status": "pending", 1781 + "created_at": "2025-12-24T01:02:12.402352600-05:00", 1782 + "updated_at": "2025-12-24T01:02:12.402352600-05:00", 1783 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1784 + }, 1785 + { 1786 + "id": 163, 1787 + "change_id": "6a840fe3-5500-4fbf-8f8f-c3e25e2182c6", 1788 + "node_type": "action", 1789 + "title": "Refactoring SetupWizard to use Toggle and DropdownWithIcons", 1790 + "description": null, 1791 + "status": "pending", 1792 + "created_at": "2025-12-24T01:03:20.596911400-05:00", 1793 + "updated_at": "2025-12-24T01:03:20.596911400-05:00", 1794 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 1795 + }, 1796 + { 1797 + "id": 164, 1798 + "change_id": "70c0a116-1c27-4d07-b1b7-4eaf6fe094e3", 1799 + "node_type": "outcome", 1800 + "title": "Refactored SetupWizard and Settings to use shared Toggle and DropdownWithIcons components. Build passes. Bundle size unchanged (284.39 KB)", 1801 + "description": null, 1802 + "status": "pending", 1803 + "created_at": "2025-12-24T01:05:57.779632600-05:00", 1804 + "updated_at": "2025-12-24T01:05:57.779632600-05:00", 1805 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 1806 + }, 1807 + { 1808 + "id": 165, 1809 + "change_id": "2ec5c20a-6d22-40b5-bf9c-ebddbd08d6e8", 1810 + "node_type": "action", 1811 + "title": "Committing shared component refactoring", 1812 + "description": null, 1813 + "status": "pending", 1814 + "created_at": "2025-12-24T01:07:44.847703900-05:00", 1815 + "updated_at": "2025-12-24T01:07:44.847703900-05:00", 1816 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"6cd4d62\",\"confidence\":95}" 1542 1817 } 1543 1818 ], 1544 1819 "edges": [ ··· 3037 3312 "weight": 1.0, 3038 3313 "rationale": "Committed to git", 3039 3314 "created_at": "2025-12-23T21:11:22.724479-05:00" 3315 + }, 3316 + { 3317 + "id": 137, 3318 + "from_node_id": 122, 3319 + "to_node_id": 141, 3320 + "from_change_id": "0357c76e-2c91-46e7-941e-4466d520b8c2", 3321 + "to_change_id": "954f8b75-05eb-44de-afd6-89c349587b2e", 3322 + "edge_type": "leads_to", 3323 + "weight": 1.0, 3324 + "rationale": "Session complete", 3325 + "created_at": "2025-12-23T21:11:47.686638-05:00" 3326 + }, 3327 + { 3328 + "id": 138, 3329 + "from_node_id": 128, 3330 + "to_node_id": 142, 3331 + "from_change_id": "94d4ca47-69ed-4cbd-91b8-652936b01b98", 3332 + "to_change_id": "6b5a1e7d-27d5-4a7a-9eba-8c0495987ba6", 3333 + "edge_type": "leads_to", 3334 + "weight": 1.0, 3335 + "rationale": "Previous dynamic size fix didn't solve spacing issue, need fixed gap approach", 3336 + "created_at": "2025-12-23T21:22:46.652776-05:00" 3337 + }, 3338 + { 3339 + "id": 139, 3340 + "from_node_id": 142, 3341 + "to_node_id": 143, 3342 + "from_change_id": "6b5a1e7d-27d5-4a7a-9eba-8c0495987ba6", 3343 + "to_change_id": "b6a1f3ef-068a-4ae7-a3a4-130fe830fe4c", 3344 + "edge_type": "leads_to", 3345 + "weight": 1.0, 3346 + "rationale": "Analysis of current implementation", 3347 + "created_at": "2025-12-23T21:23:24.547472-05:00" 3348 + }, 3349 + { 3350 + "id": 140, 3351 + "from_node_id": 143, 3352 + "to_node_id": 144, 3353 + "from_change_id": "b6a1f3ef-068a-4ae7-a3a4-130fe830fe4c", 3354 + "to_change_id": "37bbbbd0-0fdd-4ce3-aa86-dd88d45513fc", 3355 + "edge_type": "leads_to", 3356 + "weight": 1.0, 3357 + "rationale": "Implementation complete", 3358 + "created_at": "2025-12-23T21:24:19.701031200-05:00" 3359 + }, 3360 + { 3361 + "id": 141, 3362 + "from_node_id": 122, 3363 + "to_node_id": 142, 3364 + "from_change_id": "0357c76e-2c91-46e7-941e-4466d520b8c2", 3365 + "to_change_id": "6b5a1e7d-27d5-4a7a-9eba-8c0495987ba6", 3366 + "edge_type": "leads_to", 3367 + "weight": 1.0, 3368 + "rationale": "Additional bug fix - card spacing issue", 3369 + "created_at": "2025-12-23T21:24:27.116742400-05:00" 3370 + }, 3371 + { 3372 + "id": 142, 3373 + "from_node_id": 142, 3374 + "to_node_id": 145, 3375 + "from_change_id": "6b5a1e7d-27d5-4a7a-9eba-8c0495987ba6", 3376 + "to_change_id": "e8b327a0-372f-478b-a33a-2e1ce554bed2", 3377 + "edge_type": "leads_to", 3378 + "weight": 1.0, 3379 + "rationale": "Related alignment issue in results cards", 3380 + "created_at": "2025-12-23T21:46:18.418360700-05:00" 3381 + }, 3382 + { 3383 + "id": 143, 3384 + "from_node_id": 145, 3385 + "to_node_id": 146, 3386 + "from_change_id": "e8b327a0-372f-478b-a33a-2e1ce554bed2", 3387 + "to_change_id": "9eae410e-0b10-442f-8291-6f54b9e2f900", 3388 + "edge_type": "leads_to", 3389 + "weight": 1.0, 3390 + "rationale": "Analysis complete", 3391 + "created_at": "2025-12-23T21:48:59.917425500-05:00" 3392 + }, 3393 + { 3394 + "id": 144, 3395 + "from_node_id": 146, 3396 + "to_node_id": 147, 3397 + "from_change_id": "9eae410e-0b10-442f-8291-6f54b9e2f900", 3398 + "to_change_id": "31952cd1-2b20-4df9-b99e-2a8fecd45845", 3399 + "edge_type": "leads_to", 3400 + "weight": 1.0, 3401 + "rationale": "Fix implemented", 3402 + "created_at": "2025-12-23T21:50:53.429967100-05:00" 3403 + }, 3404 + { 3405 + "id": 145, 3406 + "from_node_id": 147, 3407 + "to_node_id": 148, 3408 + "from_change_id": "31952cd1-2b20-4df9-b99e-2a8fecd45845", 3409 + "to_change_id": "adec9fe9-7462-4734-9b87-ab30558da687", 3410 + "edge_type": "leads_to", 3411 + "weight": 1.0, 3412 + "rationale": "Applying consistent spacing across components", 3413 + "created_at": "2025-12-23T21:59:30.811465900-05:00" 3414 + }, 3415 + { 3416 + "id": 146, 3417 + "from_node_id": 148, 3418 + "to_node_id": 149, 3419 + "from_change_id": "adec9fe9-7462-4734-9b87-ab30558da687", 3420 + "to_change_id": "6b36b07d-75af-49a7-a095-7ff93ef9f4db", 3421 + "edge_type": "leads_to", 3422 + "weight": 1.0, 3423 + "rationale": "Implementation complete", 3424 + "created_at": "2025-12-23T22:02:20.157839200-05:00" 3425 + }, 3426 + { 3427 + "id": 147, 3428 + "from_node_id": 149, 3429 + "to_node_id": 150, 3430 + "from_change_id": "6b36b07d-75af-49a7-a095-7ff93ef9f4db", 3431 + "to_change_id": "a11f624c-93ae-49e7-81e1-d8d68ac9eca8", 3432 + "edge_type": "leads_to", 3433 + "weight": 1.0, 3434 + "rationale": "Previous fix incorrect - need structural change", 3435 + "created_at": "2025-12-23T22:06:41.194038-05:00" 3436 + }, 3437 + { 3438 + "id": 148, 3439 + "from_node_id": 150, 3440 + "to_node_id": 151, 3441 + "from_change_id": "a11f624c-93ae-49e7-81e1-d8d68ac9eca8", 3442 + "to_change_id": "87d57666-91a4-45c9-a3f1-628dcc395db3", 3443 + "edge_type": "leads_to", 3444 + "weight": 1.0, 3445 + "rationale": "Restructure complete", 3446 + "created_at": "2025-12-23T22:08:30.246959100-05:00" 3447 + }, 3448 + { 3449 + "id": 149, 3450 + "from_node_id": 151, 3451 + "to_node_id": 152, 3452 + "from_change_id": "87d57666-91a4-45c9-a3f1-628dcc395db3", 3453 + "to_change_id": "eb1b6810-9528-4e4e-b35e-43a02ccf8f3c", 3454 + "edge_type": "leads_to", 3455 + "weight": 1.0, 3456 + "rationale": "Exploring further abstraction", 3457 + "created_at": "2025-12-23T22:28:46.077917-05:00" 3458 + }, 3459 + { 3460 + "id": 150, 3461 + "from_node_id": 152, 3462 + "to_node_id": 153, 3463 + "from_change_id": "eb1b6810-9528-4e4e-b35e-43a02ccf8f3c", 3464 + "to_change_id": "0099b286-7090-459e-8fc6-1abccf3488bb", 3465 + "edge_type": "leads_to", 3466 + "weight": 1.0, 3467 + "rationale": "Implementation approved", 3468 + "created_at": "2025-12-24T00:20:18.402987400-05:00" 3469 + }, 3470 + { 3471 + "id": 151, 3472 + "from_node_id": 153, 3473 + "to_node_id": 154, 3474 + "from_change_id": "0099b286-7090-459e-8fc6-1abccf3488bb", 3475 + "to_change_id": "41384be8-f128-4424-9419-2626fc7ac8f4", 3476 + "edge_type": "leads_to", 3477 + "weight": 1.0, 3478 + "rationale": "Refactor complete", 3479 + "created_at": "2025-12-24T00:22:12.798856800-05:00" 3480 + }, 3481 + { 3482 + "id": 152, 3483 + "from_node_id": 110, 3484 + "to_node_id": 122, 3485 + "from_change_id": "22b9c3db-9f95-45d7-a3ed-bdfac54677db", 3486 + "to_change_id": "0357c76e-2c91-46e7-941e-4466d520b8c2", 3487 + "edge_type": "leads_to", 3488 + "weight": 1.0, 3489 + "rationale": "UX bug fixes discovered during cleanup/testing phase", 3490 + "created_at": "2025-12-24T00:46:02.097576300-05:00" 3491 + }, 3492 + { 3493 + "id": 153, 3494 + "from_node_id": 155, 3495 + "to_node_id": 156, 3496 + "from_change_id": "76e2700c-0324-4563-81d1-b31dc3e85460", 3497 + "to_change_id": "28f2b394-61e7-4840-9c33-838a1bcf0e04", 3498 + "edge_type": "leads_to", 3499 + "weight": 1.0, 3500 + "rationale": "Initial exploration", 3501 + "created_at": "2025-12-24T00:59:34.910795600-05:00" 3502 + }, 3503 + { 3504 + "id": 154, 3505 + "from_node_id": 155, 3506 + "to_node_id": 157, 3507 + "from_change_id": "76e2700c-0324-4563-81d1-b31dc3e85460", 3508 + "to_change_id": "3b149b2b-0e2d-4e38-a2ed-858836cb2e24", 3509 + "edge_type": "leads_to", 3510 + "weight": 1.0, 3511 + "rationale": "Pattern analysis", 3512 + "created_at": "2025-12-24T01:00:00.710587900-05:00" 3513 + }, 3514 + { 3515 + "id": 155, 3516 + "from_node_id": 157, 3517 + "to_node_id": 158, 3518 + "from_change_id": "3b149b2b-0e2d-4e38-a2ed-858836cb2e24", 3519 + "to_change_id": "37ab3ef1-445b-46cc-bf51-25ad24f6c131", 3520 + "edge_type": "leads_to", 3521 + "weight": 1.0, 3522 + "rationale": "Needs architecture decision", 3523 + "created_at": "2025-12-24T01:00:00.835830200-05:00" 3524 + }, 3525 + { 3526 + "id": 156, 3527 + "from_node_id": 158, 3528 + "to_node_id": 159, 3529 + "from_change_id": "37ab3ef1-445b-46cc-bf51-25ad24f6c131", 3530 + "to_change_id": "8a6cd252-e3a1-4c68-a50f-05f3574016ed", 3531 + "edge_type": "leads_to", 3532 + "weight": 1.0, 3533 + "rationale": "Option", 3534 + "created_at": "2025-12-24T01:00:00.947049900-05:00" 3535 + }, 3536 + { 3537 + "id": 157, 3538 + "from_node_id": 158, 3539 + "to_node_id": 160, 3540 + "from_change_id": "37ab3ef1-445b-46cc-bf51-25ad24f6c131", 3541 + "to_change_id": "369d275f-5ad7-4d13-aad6-7bcd69ef9e3d", 3542 + "edge_type": "leads_to", 3543 + "weight": 1.0, 3544 + "rationale": "Decision made", 3545 + "created_at": "2025-12-24T01:00:01.064823900-05:00" 3546 + }, 3547 + { 3548 + "id": 158, 3549 + "from_node_id": 160, 3550 + "to_node_id": 161, 3551 + "from_change_id": "369d275f-5ad7-4d13-aad6-7bcd69ef9e3d", 3552 + "to_change_id": "3e38a61f-16f1-4a68-a943-e66b11df29ae", 3553 + "edge_type": "leads_to", 3554 + "weight": 1.0, 3555 + "rationale": "Implementation action", 3556 + "created_at": "2025-12-24T01:00:31.107625600-05:00" 3557 + }, 3558 + { 3559 + "id": 159, 3560 + "from_node_id": 161, 3561 + "to_node_id": 162, 3562 + "from_change_id": "3e38a61f-16f1-4a68-a943-e66b11df29ae", 3563 + "to_change_id": "1be7b49c-5da2-4b2c-bd81-7900fdcf8584", 3564 + "edge_type": "leads_to", 3565 + "weight": 1.0, 3566 + "rationale": "Components created", 3567 + "created_at": "2025-12-24T01:02:14.259713700-05:00" 3568 + }, 3569 + { 3570 + "id": 160, 3571 + "from_node_id": 162, 3572 + "to_node_id": 163, 3573 + "from_change_id": "1be7b49c-5da2-4b2c-bd81-7900fdcf8584", 3574 + "to_change_id": "6a840fe3-5500-4fbf-8f8f-c3e25e2182c6", 3575 + "edge_type": "leads_to", 3576 + "weight": 1.0, 3577 + "rationale": "Next refactoring step", 3578 + "created_at": "2025-12-24T01:03:35.697505700-05:00" 3579 + }, 3580 + { 3581 + "id": 161, 3582 + "from_node_id": 163, 3583 + "to_node_id": 164, 3584 + "from_change_id": "6a840fe3-5500-4fbf-8f8f-c3e25e2182c6", 3585 + "to_change_id": "70c0a116-1c27-4d07-b1b7-4eaf6fe094e3", 3586 + "edge_type": "leads_to", 3587 + "weight": 1.0, 3588 + "rationale": "Refactoring complete", 3589 + "created_at": "2025-12-24T01:05:59.564455400-05:00" 3590 + }, 3591 + { 3592 + "id": 162, 3593 + "from_node_id": 164, 3594 + "to_node_id": 165, 3595 + "from_change_id": "70c0a116-1c27-4d07-b1b7-4eaf6fe094e3", 3596 + "to_change_id": "2ec5c20a-6d22-40b5-bf9c-ebddbd08d6e8", 3597 + "edge_type": "leads_to", 3598 + "weight": 1.0, 3599 + "rationale": "Commit action", 3600 + "created_at": "2025-12-24T01:07:46.216122700-05:00" 3040 3601 } 3041 3602 ] 3042 3603 }
+39
src/components/common/Dropdown.tsx
··· 1 + import React from "react"; 2 + 3 + export interface DropdownOption { 4 + value: string; 5 + label: string; 6 + icon?: string; // URL to icon (e.g., favicon.ico) 7 + } 8 + 9 + interface DropdownProps { 10 + value: string; 11 + onChange: (value: string) => void; 12 + options: DropdownOption[]; 13 + className?: string; 14 + fullWidth?: boolean; 15 + } 16 + 17 + const Dropdown: React.FC<DropdownProps> = ({ 18 + value, 19 + onChange, 20 + options, 21 + className = "", 22 + fullWidth = false, 23 + }) => { 24 + return ( 25 + <select 26 + value={value} 27 + onChange={(e) => onChange(e.target.value)} 28 + className={`px-3 py-2 bg-white dark:bg-slate-800 border border-cyan-500/30 dark:border-purple-500/30 rounded-lg text-sm text-purple-950 dark:text-cyan-50 hover:border-cyan-400 dark:hover:border-purple-400 focus:outline-none focus:ring-2 focus:ring-orange-500 dark:focus:ring-amber-400 transition-colors ${fullWidth ? "w-full" : ""} ${className}`} 29 + > 30 + {options.map((option) => ( 31 + <option key={option.value} value={option.value}> 32 + {option.label} 33 + </option> 34 + ))} 35 + </select> 36 + ); 37 + }; 38 + 39 + export default Dropdown;
+124
src/components/common/DropdownWithIcons.tsx
··· 1 + import React, { useState, useRef, useEffect } from "react"; 2 + import { ChevronDown, Check } from "lucide-react"; 3 + 4 + export interface DropdownOptionWithIcon { 5 + value: string; 6 + label: string; 7 + icon?: string; // URL to icon (e.g., favicon.ico) 8 + } 9 + 10 + interface DropdownWithIconsProps { 11 + value: string; 12 + onChange: (value: string) => void; 13 + options: DropdownOptionWithIcon[]; 14 + className?: string; 15 + placeholder?: string; 16 + } 17 + 18 + /** 19 + * Custom dropdown component with icon support. 20 + * Shows icons next to option text similar to history card social links. 21 + * Falls back to native select if no icons provided. 22 + */ 23 + const DropdownWithIcons: React.FC<DropdownWithIconsProps> = ({ 24 + value, 25 + onChange, 26 + options, 27 + className = "", 28 + placeholder = "Select...", 29 + }) => { 30 + const [isOpen, setIsOpen] = useState(false); 31 + const dropdownRef = useRef<HTMLDivElement>(null); 32 + const hasIcons = options.some((opt) => opt.icon); 33 + 34 + // Close dropdown when clicking outside 35 + useEffect(() => { 36 + const handleClickOutside = (event: MouseEvent) => { 37 + if ( 38 + dropdownRef.current && 39 + !dropdownRef.current.contains(event.target as Node) 40 + ) { 41 + setIsOpen(false); 42 + } 43 + }; 44 + 45 + if (isOpen) { 46 + document.addEventListener("mousedown", handleClickOutside); 47 + return () => { 48 + document.removeEventListener("mousedown", handleClickOutside); 49 + }; 50 + } 51 + }, [isOpen]); 52 + 53 + const selectedOption = options.find((opt) => opt.value === value); 54 + 55 + // If no icons, use native select for better accessibility 56 + if (!hasIcons) { 57 + return ( 58 + <select 59 + value={value} 60 + onChange={(e) => onChange(e.target.value)} 61 + className={`px-3 py-2 bg-white dark:bg-slate-800 border border-cyan-500/30 dark:border-purple-500/30 rounded-lg text-sm text-purple-950 dark:text-cyan-50 hover:border-cyan-400 dark:hover:border-purple-400 focus:outline-none focus:ring-2 focus:ring-orange-500 dark:focus:ring-amber-400 transition-colors ${className}`} 62 + > 63 + {options.map((option) => ( 64 + <option key={option.value} value={option.value}> 65 + {option.label} 66 + </option> 67 + ))} 68 + </select> 69 + ); 70 + } 71 + 72 + // Custom dropdown with icons 73 + return ( 74 + <div ref={dropdownRef} className={`relative ${className}`}> 75 + <button 76 + type="button" 77 + onClick={() => setIsOpen(!isOpen)} 78 + className="w-full px-3 py-2 bg-white dark:bg-slate-800 border border-cyan-500/30 dark:border-purple-500/30 rounded-lg text-sm text-purple-950 dark:text-cyan-50 hover:border-cyan-400 dark:hover:border-purple-400 focus:outline-none focus:ring-2 focus:ring-orange-500 dark:focus:ring-amber-400 transition-colors flex items-center justify-between" 79 + > 80 + <span className="flex items-center gap-2"> 81 + {selectedOption?.icon && ( 82 + <img 83 + src={selectedOption.icon} 84 + alt="" 85 + className="w-4 h-4 flex-shrink-0" 86 + /> 87 + )} 88 + <span>{selectedOption?.label || placeholder}</span> 89 + </span> 90 + <ChevronDown 91 + className={`w-4 h-4 transition-transform ${isOpen ? "rotate-180" : ""}`} 92 + /> 93 + </button> 94 + 95 + {isOpen && ( 96 + <div className="absolute z-10 w-full mt-1 bg-white dark:bg-slate-800 border border-cyan-500/30 dark:border-purple-500/30 rounded-lg shadow-lg max-h-60 overflow-auto"> 97 + {options.map((option) => ( 98 + <button 99 + key={option.value} 100 + type="button" 101 + onClick={() => { 102 + onChange(option.value); 103 + setIsOpen(false); 104 + }} 105 + className="w-full px-3 py-2 text-sm text-left hover:bg-purple-100/50 dark:hover:bg-slate-700/50 transition-colors flex items-center gap-2 relative" 106 + > 107 + {option.icon && ( 108 + <img src={option.icon} alt="" className="w-4 h-4 flex-shrink-0" /> 109 + )} 110 + <span className="flex-1 text-purple-950 dark:text-cyan-50"> 111 + {option.label} 112 + </span> 113 + {option.value === value && ( 114 + <Check className="w-4 h-4 text-orange-500 dark:text-amber-400" /> 115 + )} 116 + </button> 117 + ))} 118 + </div> 119 + )} 120 + </div> 121 + ); 122 + }; 123 + 124 + export default DropdownWithIcons;
+49
src/components/common/Toggle.tsx
··· 1 + import React from "react"; 2 + 3 + interface ToggleProps { 4 + checked: boolean; 5 + onChange: (checked: boolean) => void; 6 + disabled?: boolean; 7 + label: string; 8 + description?: string; 9 + id?: string; 10 + } 11 + 12 + const Toggle: React.FC<ToggleProps> = ({ 13 + checked, 14 + onChange, 15 + disabled = false, 16 + label, 17 + description, 18 + id, 19 + }) => { 20 + const toggleId = id || `toggle-${label.toLowerCase().replace(/\s+/g, "-")}`; 21 + 22 + return ( 23 + <div className="flex items-start justify-between"> 24 + <div className="flex-1"> 25 + <div className="font-medium text-purple-950 dark:text-cyan-50 mb-1"> 26 + {label} 27 + </div> 28 + {description && ( 29 + <p className="text-sm text-purple-900 dark:text-cyan-100"> 30 + {description} 31 + </p> 32 + )} 33 + </div> 34 + <label className="relative inline-flex items-center cursor-pointer ml-4"> 35 + <input 36 + type="checkbox" 37 + id={toggleId} 38 + checked={checked} 39 + onChange={(e) => onChange(e.target.checked)} 40 + disabled={disabled} 41 + className="sr-only peer" 42 + /> 43 + <div className="w-11 h-6 bg-gray-400 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-orange-650/50 dark:peer-focus:ring-amber-400/50 rounded-full peer dark:bg-gray-600 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-700 peer-checked:bg-orange-500 dark:peer-checked:bg-orange-400 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed"></div> 44 + </label> 45 + </div> 46 + ); 47 + }; 48 + 49 + export default Toggle;