()
| 125 | }; |
| 126 | |
| 127 | function App() { |
| 128 | useRegistryVersion(); |
| 129 | const names = Array.from(registered.keys()); |
| 130 | const [active, setActive] = useState(""); |
| 131 | |
| 132 | useEffect(() => { |
| 133 | if ((!active || !registered.has(active)) && names.length > 0) { |
| 134 | setActive(names[0]); |
| 135 | } |
| 136 | }, [names, active]); |
| 137 | |
| 138 | const tabs = names.map((n) => ({ name: n, label: PLUGIN_LABELS[n] || n })); |
| 139 | const Active = active ? registered.get(active) : null; |
| 140 | |
| 141 | return ( |
| 142 | <div className="ts-shell"> |
| 143 | <header className="ts-shell-header"> |
| 144 | <div className="ts-shell-brand"> |
| 145 | <span className="ts-shell-logo" aria-hidden="true"> |
| 146 | ◳ |
| 147 | </span> |
| 148 | <h1 className="ts-shell-title">tracedecay · dev</h1> |
| 149 | </div> |
| 150 | <div className="ts-shell-tabs" role="tablist" aria-label="Plugin tabs (dev)"> |
| 151 | {tabs.map((t) => ( |
| 152 | <button |
| 153 | key={t.name} |
| 154 | type="button" |
| 155 | role="tab" |
| 156 | aria-selected={t.name === active} |
| 157 | tabIndex={t.name === active ? 0 : -1} |
| 158 | className={`ts-shell-tab${t.name === active ? " ts-shell-tab-active" : ""}`} |
| 159 | onClick={() => setActive(t.name)} |
| 160 | > |
| 161 | {t.label} |
| 162 | </button> |
| 163 | ))} |
| 164 | {tabs.length === 0 && ( |
| 165 | <span style={{ padding: "0.5rem 1rem", color: "var(--ts-text-3)" }}> |
| 166 | No plugins registered… |
| 167 | </span> |
| 168 | )} |
| 169 | </div> |
| 170 | </header> |
| 171 | <main className="ts-shell-main"> |
| 172 | {Active ? ( |
| 173 | <Active /> |
| 174 | ) : ( |
| 175 | <div className="ts-shell-loading" role="status" aria-live="polite"> |
| 176 | Waiting for plugins to register… |
| 177 | </div> |
| 178 | )} |
| 179 | </main> |
| 180 | </div> |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | createRoot(document.getElementById("root")).render(<App />); |
nothing calls this directly
no test coverage detected