Commit adddcc4
authored
fix(panel): restore pre-lock content on unlockPanel — fixes ALL constructor-only premium panels (koala73#3814)
* fix(panel): restore pre-lock content on unlockPanel — fixes ALL constructor-only premium panels
Symptom: a premium-gated panel (DeductionPanel reported today; ChatAnalystPanel
was the first instance fixed surgically in PR koala73#3797) shows header-only with
a vast empty body after a FREE/anon → PRO auth-state cycle. The textarea,
buttons, and form chrome are all gone with no way to recover but a reload.
Root cause: Panel.unlockPanel() previously called replaceChildren(this.content)
to clear the lock-state CTA but never restored the subclass UI. Subclasses
whose UI is built ONCE in the constructor (no data-driven re-render path)
end up permanently empty after the first showGatedCta → unlockPanel cycle.
WEB_PREMIUM_PANELS has 10 candidate panels and 2 are now confirmed broken
(chat-analyst, deduction) — playing whack-a-mole one-override-per-panel
doesn't scale.
Fix: Panel snapshots this.content's child nodes at the moment showLocked /
showGatedCta replaces them, and unlockPanel re-attaches those same node
instances. The cache holds the ACTUAL DOM nodes (not a re-render seed) so
reattaching preserves:
- event listeners attached directly to wrapper / textarea / button nodes
- subclass references like `this.inputEl` / `this.submitBtn` (still
point at the live node)
- any in-flight UI state (a half-typed query, a partial stream bubble)
Constructor-only subclasses (DeductionPanel + the other 8 in
WEB_PREMIUM_PANELS) are repaired transparently with zero per-subclass
changes. ChatAnalystPanel's surgical override from PR koala73#3797 becomes
redundant belt-and-braces but harmless (its querySelector check finds the
restored wrapper and short-circuits the rebuild).
Re-entrancy safety: the snapshot is only taken on the FIRST transition
into lock state (`if (this._savedContent !== null) return`). A subsequent
showLocked / showGatedCta while already locked does not overwrite the
cache with the lock-state CTA — covered by a regression test.
Test plan
- New tests/panel-unlock-restore.test.mts (6 cases) using a minimal
`extends Panel` subclass with no override:
* Initial mount renders constructor-built UI
* showGatedCta → unlockPanel restores nodes by IDENTITY (===), not just
by selector — proves the same DOM instance comes back
* showLocked → unlockPanel also restores via the same snapshot path
* 3 lock/unlock cycles all restore the same node
* Re-entrant showLocked while already locked doesn't corrupt the
snapshot
* Constructor counter stays at 1 across all cycles — proves no rebuild
- Existing tests/chat-analyst-panel-unlock.test.mts still 4/4 green —
the surgical override from PR koala73#3797 now finds the restored wrapper
and short-circuits cleanly.
- npx tsc --noEmit clean.
Reverting the restore block in unlockPanel breaks exactly the 4 cases
that exercise the restore path; the 2 cases that don't (initial mount,
never-locked unlock) stay green. Confirms the test bites the right
regression.
Bundle pattern for the test follows tests/helpers/chat-analyst-panel-harness.mjs
but uses an esbuild VIRTUAL ENTRY containing a minimal subclass source
string, so the test exercises the REAL Panel base-class restore path
without depending on any specific premium panel's full module graph.
* fix(panel): destroy clears _savedContent + showGatedCta early-return ordering (PR koala73#3814 review)
Two Greptile P2 nits:
1. _savedContent was not cleared in destroy(). Every other DOM-referencing
member is explicitly nulled there (pendingContentHtml, tooltipCloseHandler,
onTouchMove, …) — _savedContent now matches that pattern. A panel
destroyed while still in the locked state no longer retains the detached
pre-lock DOM subtree for the lifetime of the Panel instance.
2. showGatedCta() set _locked, hid header siblings, added panel-is-locked,
AND snapshotted content BEFORE the if (!entry) return guard for
PanelGateReason.NONE. The reason map intentionally doesn't list NONE
(it should never reach here in the updatePanelGating flow), but the
guard left the panel visually half-locked with no CTA on that path.
Moved the side-effects AFTER the guard so the impossible path is a
true no-op. Happy paths (FREE_TIER, ANONYMOUS) are behaviorally
unchanged.
Test: added a 7th case "showGatedCta with an unknown reason is a clean
no-op (no half-locked state)" that asserts panel-is-locked class is NOT
applied + the wrapper/input nodes stay untouched + no snapshot was taken
(verified by post-unlock node identity). Reverting just the reordering
flips ONLY this new test red; the other 6 happy-path cases stay green.
Confirms the test bites the right behavior.
11/11 tests pass; tsc clean.1 parent 137e93f commit adddcc4
3 files changed
Lines changed: 489 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
238 | 238 | | |
239 | 239 | | |
240 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
241 | 249 | | |
242 | 250 | | |
243 | 251 | | |
| |||
831 | 839 | | |
832 | 840 | | |
833 | 841 | | |
| 842 | + | |
834 | 843 | | |
835 | 844 | | |
836 | 845 | | |
| |||
869 | 878 | | |
870 | 879 | | |
871 | 880 | | |
872 | | - | |
873 | | - | |
874 | | - | |
875 | | - | |
876 | | - | |
877 | | - | |
878 | | - | |
879 | | - | |
880 | | - | |
881 | 881 | | |
882 | 882 | | |
883 | 883 | | |
| |||
894 | 894 | | |
895 | 895 | | |
896 | 896 | | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
897 | 911 | | |
898 | 912 | | |
899 | 913 | | |
| |||
913 | 927 | | |
914 | 928 | | |
915 | 929 | | |
916 | | - | |
917 | | - | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
918 | 951 | | |
919 | 952 | | |
920 | 953 | | |
| |||
1143 | 1176 | | |
1144 | 1177 | | |
1145 | 1178 | | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
1146 | 1183 | | |
1147 | 1184 | | |
1148 | 1185 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
0 commit comments