$npx -y skills add nowledge-co/con-terminal --skill gpui-cache-awareUse this skill when reviewing or changing Con UI performance, especially when a view feels janky, expensive, or unexpectedly re-renders during unrelated updates.
| 1 | # GPUI Cache-Aware Review |
| 2 | |
| 3 | Use this skill when reviewing or changing Con UI performance, especially when a view feels janky, expensive, or unexpectedly re-renders during unrelated updates. |
| 4 | |
| 5 | ## Goal |
| 6 | |
| 7 | Keep Con visually rich without paying repeated GPUI layout/render cost for work that could be cached or isolated. |
| 8 | |
| 9 | ## Core Rule |
| 10 | |
| 11 | Cache at the cheapest correct layer first. |
| 12 | |
| 13 | Order of preference: |
| 14 | |
| 15 | 1. Cache parsed / normalized data. |
| 16 | 2. Cache expensive text-run or highlight transforms. |
| 17 | 3. Isolate expensive subtrees behind stable entity boundaries. |
| 18 | 4. Use GPUI `AnyView::cached(...)` only when the subtree has a stable size contract. |
| 19 | |
| 20 | Do not jump straight to view caching. |
| 21 | |
| 22 | ## Review Checklist |
| 23 | |
| 24 | For any slow UI path, inspect these in order: |
| 25 | |
| 26 | ### 1. Parse / transform churn |
| 27 | |
| 28 | - Is the code reparsing markdown / JSON / syntax / layout input on every render? |
| 29 | - Is the code rebuilding `SharedString`, `Vec<TextRun>`, highlighted runs, or table cell text on every render? |
| 30 | - Can the expensive transform be retained on the model object and invalidated only on real content change? |
| 31 | |
| 32 | Preferred fix: |
| 33 | |
| 34 | - add data-layer caches on parsed document/block/cell/message structs |
| 35 | - invalidate only when source text or theme-dependent key changes |
| 36 | |
| 37 | ### 2. Render-tree cardinality |
| 38 | |
| 39 | - Is the renderer producing many tiny `div()` children for content that could be one `StyledText`? |
| 40 | - Are inline chips, per-token wrappers, nested flex rows, or deep container stacks used for long-form content? |
| 41 | |
| 42 | Preferred fix: |
| 43 | |
| 44 | - collapse long-form prose to text-first rendering |
| 45 | - keep decorative element composition for UI chrome and short content only |
| 46 | |
| 47 | ### 3. Entity boundaries |
| 48 | |
| 49 | - Is a very large subtree being rebuilt because the parent panel rerendered for unrelated state? |
| 50 | - Can the subtree live in its own `Entity<V>` with a narrower invalidation surface? |
| 51 | |
| 52 | Preferred fix: |
| 53 | |
| 54 | - split large stable regions into their own render entities |
| 55 | - keep mutation paths explicit so only that entity gets `notify()` |
| 56 | |
| 57 | ### 4. GPUI cached view suitability |
| 58 | |
| 59 | Use `AnyView::cached(...)` only if all of these are true: |
| 60 | |
| 61 | - the subtree size is externally constrained or stable |
| 62 | - the cached style can describe the layout contract correctly |
| 63 | - reusing previous layout/paint is actually valid for the subtree |
| 64 | |
| 65 | Good fits: |
| 66 | |
| 67 | - panes that fill known bounds |
| 68 | - fixed-size or externally-sized tool panels |
| 69 | - stable canvases / editors / native-host surfaces |
| 70 | |
| 71 | Bad fits: |
| 72 | |
| 73 | - intrinsic-height rich text documents |
| 74 | - content whose height depends on wrapping and dynamic width unless that width/height contract is explicitly handled |
| 75 | |
| 76 | If a cached view causes overlap, clipping, or stale layout, the cache boundary is wrong. |
| 77 | |
| 78 | ### 5. Lists and scrolling surfaces |
| 79 | |
| 80 | - If there are many repeated items, consider virtualization before micro-optimizing item chrome. |
| 81 | - If the item count is small but each item is expensive, focus on item-level caches instead. |
| 82 | |
| 83 | ## Con-Specific Guidance |
| 84 | |
| 85 | ### Markdown and chat surfaces |
| 86 | |
| 87 | - Prefer parsed markdown caches on the message/document model. |
| 88 | - Cache inline text-run generation for paragraphs, headings, and table cells. |
| 89 | - Cache syntax-highlight runs for code blocks. |
| 90 | - Avoid per-token flex trees for long replies. |
| 91 | - For long assistant replies, isolate each markdown block behind a stable `Entity` |
| 92 | and reuse that entity across parent renders. Keep per-block UI state such as |
| 93 | table scroll handles on that entity, not inside parsed markdown data that is |
| 94 | produced off the UI thread. |
| 95 | - Do not hide/fold long replies as the primary performance fix. Folding can be a |
| 96 | UX affordance, but the expanded state must remain responsive. |
| 97 | |
| 98 | ### Terminal-adjacent UI |
| 99 | |
| 100 | - Keep terminal surfaces and heavy side panels isolated. |
| 101 | - Avoid reading terminal runtime state during ordinary render unless already cached. |
| 102 | - Be cautious with transparency, animation, and resize interactions; measure before adding visual layers. |
| 103 | - For hover affordances that only need cursor feedback, prefer a tiny overlay / |
| 104 | hitbox and `cursor_pointer()` over repainting terminal text. Keep the overlay |
| 105 | state bounded to the hovered link/range. |
| 106 | |
| 107 | ### GPUI interaction gotchas |
| 108 | |
| 109 | - `.hover(|style| ...)` is a style hook on `Div`; `.on_hover(...)` is an |
| 110 | enter/exit listener and requires a `Stateful<Div>`. |
| 111 | - If a view needs `.on_hover(...)`, call `.id(stable_id)` first. Use a stable id |
| 112 | such as the pane's `FocusHandle`, not a changing row/index. |
| 113 | - For platform-selected files (`ghostty_view.rs`, `windows_view.rs`, |
| 114 | `linux_view.rs`), a macOS `cargo check -p con` only checks the macOS path. |
| 115 | Validate the touched platform path on its target or wait for portable CI |
| 116 | before treating the fix as closed. |
| 117 | |
| 118 | ## Validation |
| 119 | |
| 120 | After a cache-related change: |
| 121 | |
| 122 | - verify `cargo check -p con` |
| 123 | - run targeted tests if the subsystem has them |
| 124 | - confirm there is no layout regression |
| 125 | - confirm the cache invalidates on real content/theme changes |
| 126 | - confirm the cache does not hide stale data |
| 127 | - if touching Windows/Linux-only view code from macOS, note that local checks are |
| 128 | partial unless the target toolch |