- Add IconBadge component with sm/md/lg variants
- Refactor FeatureCard to use IconBadge
- Update ArchHighlight and RepoInfo for consistency
- Add DASHBOARD_GITHUB_TOKEN to CI for rate limit
Equatable views can opt into render caching with .equatable(). On cache hit,
the entire subtree is skipped and the cached FrameBuffer returned directly.
Cache is cleared on every @State change; GC removes stale entries per frame.
AppHeader is rendered at the top of the terminal by RenderLoop, similar to
StatusBar at the bottom. Views declare header content via .appHeader { }
ViewBuilder modifier. Supports standard (thin divider) and block (half-block
with appHeaderBackground) appearance. Hidden when no content is set.
Diff cache invalidates on header height changes to prevent ghosting.
Update IDETemplateMacros.plist and replace headers in 136 Swift files with
new format: 🖥️ TUIKit — Terminal UI Kit for Swift. Remove sdsd.swift template draft.
DimmedModifier now strips all ANSI codes and ornament characters, re-renders
with uniform palette.foregroundTertiary on palette.overlayBackground.
FrameBuffer.insertOverlay preserves base ANSI styling via ansiAwarePrefix/Suffix
and leadingANSISequences restoration. Overlays center relative to terminal size
with -2 vertical offset. Alert presets no longer color borders — only titles.
ModalPresentationModifier and AlertPresentationModifier now register
dedicated focus sections (__modal__/__alert__) and activate them instead
of clearing the entire FocusManager. Modal buttons register in the modal
section. When the modal closes, the section is not re-registered and
endRenderPass() falls back to the previous active section automatically.
New components:
- PulseTimer: GCD-based timer driving a sine-wave phase (0→1→0, 3s cycle)
- Color.lerp(): RGB linear interpolation between two colors
- BorderRenderer: standardTopBorder accepts focusIndicatorColor for ● rendering
Integration:
- FocusSectionModifier computes interpolated accent color when section is active
- RenderContext carries pulsePhase (from PulseTimer) and focusIndicatorColor
- BorderedView and ContainerView consume the indicator color on first border,
then nil it out so nested borders don't duplicate the indicator
- AppRunner owns PulseTimer, passes phase to RenderLoop each frame
RenderLoop called focusManager.clear() every frame, which reset the
active section and focused element. Replaced with beginRenderPass()
(clears sections/focusables for re-registration) and endRenderPass()
(validates preserved activeSectionID/focusedID against re-registered
sections, falls back to first available if removed from tree).
FocusManager.dispatchKeyEvent now intercepts Up/Down arrows to navigate
between focusable elements within the active section. Tab/Shift+Tab
continues to cycle between sections. Views needing custom Up/Down
handling (e.g. Menu) register via KeyEventDispatcher (Layer 2), which
takes priority over the FocusManager (Layer 3).
FocusManager reorganized from flat focusable list to section-based architecture.
Each section groups its own focusable elements. Tab cycles sections when multiple
exist, falls back to within-section cycling for single section (backward compat).
New types: FocusSection, FocusSectionModifier
New API: .focusSection("id"), register(_:inSection:), activateNextSection()
RenderContext carries activeFocusSectionID for child registration routing.
AppStateTests: Tests AppState as isolated instances without touching
RenderNotifier.current. Removed .serialized (no shared state).
StateStorageIdentityTests: Replaced testEnvironment() with testStorage()
that only creates a StateStorage. No longer sets RenderNotifier.current.
StateBox.didSet harmlessly calls setNeedsRender() on the default global.
StatePropertyTests: The one test that needs RenderNotifier.current
(stateTriggerRender) now swaps it atomically — set, mutate, check,
restore in a single synchronous sequence with no yield points.
Root cause: All three suites ran .serialized internally but parallel
to each other. Since RenderNotifier.current is a process-global static
variable, concurrent writes caused one suite to overwrite another's
AppState, making needsRender checks fail intermittently.
OverlaysPage now manages its own StatusBar items based on modal state:
- Modal open: ESC → close modal, Enter → dismiss
- Modal closed: ESC → back to menu, arrows → nav, Enter → show
Removes redundant ESC handlers from Modal/AlertPresentationModifier.
The correct approach is context-aware StatusBar items, not multiple
competing ESC handlers.
- Wire FocusManager.dispatchKeyEvent() into InputHandler as Layer 3
(Tab/Shift+Tab navigation, Enter/Space button activation)
- Modal/Alert presenters isolate base content from focus/key systems
using RenderContext.isolatedForBackground() so only modal buttons
receive focus and key events
- ESC automatically dismisses any modal or alert (framework-level)
- Fix ContainerView footer layout: Spacer() now fills correctly by
constraining footer context to actual inner width
- Remove body background color from standard style containers
(only block style uses distinct section backgrounds)
- Replace all hardcoded ANSI colors with palette semantic colors
(.palette.warning/error/info/success) in Alert presets and example app
Changed dismissButton to use HStack { Spacer(); Button } pattern for
consistent right-alignment across all alerts, dialogs, and modals.
Also simplified Alert preset usage — replaced explicit Alert<Button>.warning()
with standard Alert() initializer to avoid generic type inference issues.