@hitch42/applet 0.1.0-beta.9 → 0.1.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/_internal/client.js +120 -11
  2. package/dist/_internal/log-context.d.ts +34 -0
  3. package/dist/_internal/log-context.js +2 -0
  4. package/dist/_internal/server.js +112 -57
  5. package/dist/applet.js +12 -12
  6. package/dist/build-Bh_S5BUv.js +2 -0
  7. package/dist/{build-CAb26kMY.js → build-CEfFoIZZ.js} +1 -1
  8. package/dist/{ci-5qWl7lT3.js → ci-BKw14X2h.js} +14 -13
  9. package/dist/client.d.ts +2 -2
  10. package/dist/{contract-rP_eV9PK.d.ts → contract-CsUpTBe0.d.ts} +1 -1
  11. package/dist/{deploy-D_OFJ6-Q.js → deploy-wGYjwXrx.js} +139 -88
  12. package/dist/{generate-CBPnHoqX.js → generate-BEIYs17x.js} +54 -22
  13. package/dist/generate-Dox-OYt6.js +2 -0
  14. package/dist/headers-DkNr9MLJ.js +17 -0
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.js +2 -1
  17. package/dist/{init-tfzwuYnk.js → init-6oGPM46R.js} +10 -15
  18. package/dist/{load-config-BYSxDVAl.js → load-config-xEdTXs0y.js} +2 -2
  19. package/dist/log-context-zu8U9Sjm.js +73 -0
  20. package/dist/{login-C7RKmmTe.js → login-CUx3TmFQ.js} +1 -1
  21. package/dist/{logs-DgjgAxj2.js → logs-DSjuEN0Q.js} +20 -22
  22. package/dist/{manifest-DD_SGkS6.d.ts → manifest-C6_F4bL8.d.ts} +5 -1
  23. package/dist/{manifest-Bp87Uz0a.js → manifest-CJSL9GuC.js} +20 -4
  24. package/dist/{project-D6XHllmC.js → project-BRZGd6xq.js} +197 -16
  25. package/dist/query-cache-CXmAyLv3.js +82 -0
  26. package/dist/react.d.ts +2 -1
  27. package/dist/react.js +59 -39
  28. package/dist/{schedules-dhU1yyFr.js → schedules-C8_Svr4h.js} +1 -1
  29. package/dist/server.d.ts +8 -3
  30. package/dist/server.js +29 -8
  31. package/dist/telephony.d.ts +24 -4
  32. package/dist/telephony.js +135 -16
  33. package/dist/{variables-Bwnr7oxf.js → variables-DT5RyHdH.js} +13 -13
  34. package/dist/vite.d.ts +3 -60
  35. package/dist/vite.js +50 -15
  36. package/dist/{workflow-engine-MogejNfF.js → workflow-engine-BOTzURKM.js} +1 -7
  37. package/dist/{workspace-XVtM12ZC.js → workspace-D0dnnlxL.js} +33 -38
  38. package/package.json +13 -6
  39. package/scaffold/.agents/skills/hitch-applets/SKILL.md +29 -3
  40. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +8 -0
  41. package/scaffold/.agents/skills/hitch-applets/references/markdown.md +2 -0
  42. package/scaffold/.agents/skills/hitch-applets/references/records.md +2 -0
  43. package/scaffold/.agents/skills/hitch-data/SKILL.md +2 -1
  44. package/scaffold/.agents/skills/hitch-data/references/attributes.md +7 -1
  45. package/scaffold/.agents/skills/hitch-data/references/limits.md +13 -13
  46. package/scaffold/.agents/skills/hitch-theory/SKILL.md +3 -2
  47. package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
  48. package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
  49. package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  50. package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
  51. package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
  52. package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
  53. package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
  54. package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
  55. package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
  56. package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
  57. package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
  58. package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
  59. package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
  60. package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
  61. package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
  62. package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
  63. package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
  64. package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  65. package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
  66. package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
  67. package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  68. package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
  69. package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  70. package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
  71. package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
  72. package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  73. package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
  74. package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
  75. package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  76. package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
  77. package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  78. package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  79. package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
  80. package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
  81. package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
  82. package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  83. package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
  84. package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
  85. package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
  86. package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  87. package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
  88. package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  89. package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  90. package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  91. package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  92. package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
  93. package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
  94. package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
  95. package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
  96. package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  97. package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
  98. package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  99. package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
  100. package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  101. package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
  102. package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  103. package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
  104. package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
  105. package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
  106. package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
  107. package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
  108. package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
  109. package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
  110. package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
  111. package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
  112. package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
  113. package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
  114. package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
  115. package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
  116. package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
  117. package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
  118. package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
  119. package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
  120. package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
  121. package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
  122. package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
  123. package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
  124. package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
  125. package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
  126. package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
  127. package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
  128. package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
  129. package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
  130. package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
  131. package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
  132. package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
  133. package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
  134. package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
  135. package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
  136. package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
  137. package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
  138. package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
  139. package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
  140. package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
  141. package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
  142. package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
  143. package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
  144. package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
  145. package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
  146. package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
  147. package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
  148. package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
  149. package/scaffold/.github/workflows/hitch.yml +1 -1
  150. package/scaffold/AGENTS.md +2 -2
  151. package/scaffold/applet/src/client/components/ui/alert.tsx +1 -1
  152. package/scaffold/applet/src/client/components/ui/button.tsx +1 -1
  153. package/scaffold/applet/src/client/components/ui/checkbox.tsx +1 -1
  154. package/scaffold/applet/src/client/components/ui/dialog.tsx +2 -2
  155. package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +2 -2
  156. package/scaffold/applet/src/client/components/ui/input-group.tsx +2 -2
  157. package/scaffold/applet/src/client/components/ui/select.tsx +2 -2
  158. package/scaffold/applet/src/client/components/ui/sheet.tsx +2 -2
  159. package/scaffold/applet/src/client/components/ui/spinner.tsx +2 -1
  160. package/scaffold/applet/src/client/components/ui/tabs.tsx +1 -1
  161. package/scaffold/applet/src/client/lib/utils.ts +1 -6
  162. package/scaffold/applet/src/client/styles/globals.css +2 -0
  163. package/dist/build-BfbTbt_r.js +0 -2
  164. package/dist/generate-BgepN4-b.js +0 -2
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: Avoid Barrel File Imports
3
+ impact: CRITICAL
4
+ impactDescription: 200-800ms import cost, slow builds
5
+ tags: bundle, imports, tree-shaking, barrel-files, performance
6
+ ---
7
+
8
+ ## Avoid Barrel File Imports
9
+
10
+ Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
11
+
12
+ Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
13
+
14
+ **Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
15
+
16
+ **Incorrect (imports entire library):**
17
+
18
+ ```tsx
19
+ import { Check, X, Menu } from "lucide-react";
20
+ // Loads 1,583 modules, takes ~2.8s extra in dev
21
+ // Runtime cost: 200-800ms on every cold start
22
+
23
+ import { Button, TextField } from "@mui/material";
24
+ // Loads 2,225 modules, takes ~4.2s extra in dev
25
+ ```
26
+
27
+ **Correct (direct imports):**
28
+
29
+ ```tsx
30
+ import Button from "@mui/material/Button";
31
+ import TextField from "@mui/material/TextField";
32
+ // Loads only what you use
33
+ ```
34
+
35
+ > **TypeScript warning:** Some libraries do not publish types for deep import paths. Verify that the package exports types for each subpath.
36
+
37
+ These imports reduce the modules that the browser and build tool must process.
38
+
39
+ Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Conditional Module Loading
3
+ impact: HIGH
4
+ impactDescription: loads large data only when needed
5
+ tags: bundle, conditional-loading, lazy-loading
6
+ ---
7
+
8
+ ## Conditional Module Loading
9
+
10
+ Load large data or modules only when a feature is activated.
11
+
12
+ **Example (lazy-load animation frames):**
13
+
14
+ ```tsx
15
+ function AnimationPlayer({
16
+ enabled,
17
+ setEnabled,
18
+ }: {
19
+ enabled: boolean;
20
+ setEnabled: React.Dispatch<React.SetStateAction<boolean>>;
21
+ }) {
22
+ const [frames, setFrames] = useState<Frame[] | null>(null);
23
+
24
+ useEffect(() => {
25
+ if (enabled && !frames) {
26
+ import("./animation-frames.js")
27
+ .then((mod) => setFrames(mod.frames))
28
+ .catch(() => setEnabled(false));
29
+ }
30
+ }, [enabled, frames, setEnabled]);
31
+
32
+ if (!frames) return <Skeleton />;
33
+ return <Canvas frames={frames} />;
34
+ }
35
+ ```
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: Defer Non-Critical Third-Party Libraries
3
+ impact: MEDIUM
4
+ impactDescription: reduces the initial bundle
5
+ tags: bundle, third-party, analytics, defer
6
+ ---
7
+
8
+ ## Defer Non-Critical Third-Party Libraries
9
+
10
+ Some analytics and logging components do not need to be in the initial bundle. Load them in a separate chunk.
11
+
12
+ **Incorrect (blocks initial bundle):**
13
+
14
+ ```tsx
15
+ import Analytics from "./analytics";
16
+
17
+ export default function App() {
18
+ return (
19
+ <>
20
+ <AppRoutes />
21
+ <Analytics />
22
+ </>
23
+ );
24
+ }
25
+ ```
26
+
27
+ **Correct (loads from a separate chunk):**
28
+
29
+ ```tsx
30
+ import { lazy, Suspense } from "react";
31
+
32
+ const Analytics = lazy(() => import("./analytics"));
33
+
34
+ export default function App() {
35
+ return (
36
+ <>
37
+ <AppRoutes />
38
+ <Suspense fallback={null}>
39
+ <Analytics />
40
+ </Suspense>
41
+ </>
42
+ );
43
+ }
44
+ ```
45
+
46
+ `React.lazy` loads this chunk when React first renders `<Analytics>`. This example renders it on startup, so it does not wait for idle time.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: Dynamic Imports for Heavy Components
3
+ impact: CRITICAL
4
+ impactDescription: directly affects TTI and LCP
5
+ tags: bundle, dynamic-import, code-splitting, react-lazy
6
+ ---
7
+
8
+ ## Dynamic Imports for Heavy Components
9
+
10
+ Use `lazy` and `Suspense` to load large components only when React renders them.
11
+
12
+ **Incorrect (Monaco bundles with main chunk ~300KB):**
13
+
14
+ ```tsx
15
+ import { MonacoEditor } from "./monaco-editor";
16
+
17
+ function CodePanel({ code }: { code: string }) {
18
+ return <MonacoEditor value={code} />;
19
+ }
20
+ ```
21
+
22
+ **Correct (Monaco loads on demand):**
23
+
24
+ ```tsx
25
+ import { lazy, Suspense } from "react";
26
+
27
+ const MonacoEditor = lazy(() =>
28
+ import("./monaco-editor").then((module) => ({ default: module.MonacoEditor })),
29
+ );
30
+
31
+ function CodePanel({ code }: { code: string }) {
32
+ return (
33
+ <Suspense fallback={<EditorSkeleton />}>
34
+ <MonacoEditor value={code} />
35
+ </Suspense>
36
+ );
37
+ }
38
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Preload Based on User Intent
3
+ impact: MEDIUM
4
+ impactDescription: reduces perceived latency
5
+ tags: bundle, preload, user-intent, hover
6
+ ---
7
+
8
+ ## Preload Based on User Intent
9
+
10
+ Preload heavy bundles before they're needed to reduce perceived latency.
11
+
12
+ **Example (preload on hover/focus):**
13
+
14
+ ```tsx
15
+ function EditorButton({ onClick }: { onClick: () => void }) {
16
+ const preload = () => {
17
+ void import("./monaco-editor");
18
+ };
19
+
20
+ return (
21
+ <button onMouseEnter={preload} onFocus={preload} onClick={onClick}>
22
+ Open Editor
23
+ </button>
24
+ );
25
+ }
26
+ ```
27
+
28
+ **Example (preload when feature flag is enabled):**
29
+
30
+ ```tsx
31
+ function FlagsProvider({ children, flags }: Props) {
32
+ useEffect(() => {
33
+ if (flags.editorEnabled) {
34
+ void import("./monaco-editor").then((mod) => mod.init());
35
+ }
36
+ }, [flags.editorEnabled]);
37
+
38
+ return <FlagsContext.Provider value={flags}>{children}</FlagsContext.Provider>;
39
+ }
40
+ ```
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: Deduplicate Global Event Listeners
3
+ impact: LOW
4
+ impactDescription: single listener for N components
5
+ tags: client, swr, event-listeners, subscription
6
+ ---
7
+
8
+ ## Deduplicate Global Event Listeners
9
+
10
+ Use `useSWRSubscription()` to share global event listeners across component instances.
11
+
12
+ **Incorrect (N instances = N listeners):**
13
+
14
+ ```tsx
15
+ function useKeyboardShortcut(key: string, callback: () => void) {
16
+ useEffect(() => {
17
+ const handler = (e: KeyboardEvent) => {
18
+ if (e.metaKey && e.key === key) {
19
+ callback();
20
+ }
21
+ };
22
+ window.addEventListener("keydown", handler);
23
+ return () => window.removeEventListener("keydown", handler);
24
+ }, [key, callback]);
25
+ }
26
+ ```
27
+
28
+ When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
29
+
30
+ **Correct (N instances = 1 listener):**
31
+
32
+ ```tsx
33
+ import useSWRSubscription from "swr/subscription";
34
+
35
+ // Module-level Map to track callbacks per key
36
+ const keyCallbacks = new Map<string, Set<() => void>>();
37
+
38
+ function useKeyboardShortcut(key: string, callback: () => void) {
39
+ // Register this callback in the Map
40
+ useEffect(() => {
41
+ if (!keyCallbacks.has(key)) {
42
+ keyCallbacks.set(key, new Set());
43
+ }
44
+ keyCallbacks.get(key)!.add(callback);
45
+
46
+ return () => {
47
+ const set = keyCallbacks.get(key);
48
+ if (set) {
49
+ set.delete(callback);
50
+ if (set.size === 0) {
51
+ keyCallbacks.delete(key);
52
+ }
53
+ }
54
+ };
55
+ }, [key, callback]);
56
+
57
+ useSWRSubscription("global-keydown", () => {
58
+ const handler = (e: KeyboardEvent) => {
59
+ if (e.metaKey && keyCallbacks.has(e.key)) {
60
+ keyCallbacks.get(e.key)!.forEach((cb) => cb());
61
+ }
62
+ };
63
+ window.addEventListener("keydown", handler);
64
+ return () => window.removeEventListener("keydown", handler);
65
+ });
66
+ }
67
+
68
+ function Profile() {
69
+ // Multiple shortcuts will share the same listener
70
+ useKeyboardShortcut("p", () => {
71
+ /* ... */
72
+ });
73
+ useKeyboardShortcut("k", () => {
74
+ /* ... */
75
+ });
76
+ // ...
77
+ }
78
+ ```
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: Version and Minimize localStorage Data
3
+ impact: MEDIUM
4
+ impactDescription: prevents schema conflicts, reduces storage size
5
+ tags: client, localStorage, storage, versioning, data-minimization
6
+ ---
7
+
8
+ ## Version and Minimize localStorage Data
9
+
10
+ Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ // No version, stores everything, no error handling
16
+ localStorage.setItem("userConfig", JSON.stringify(fullUserObject));
17
+ const data = localStorage.getItem("userConfig");
18
+ ```
19
+
20
+ **Correct:**
21
+
22
+ ```typescript
23
+ const VERSION = "v2";
24
+
25
+ function saveConfig(config: { theme: string; language: string }) {
26
+ try {
27
+ localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config));
28
+ } catch {
29
+ // Throws in incognito/private browsing, quota exceeded, or disabled
30
+ }
31
+ }
32
+
33
+ function loadConfig() {
34
+ try {
35
+ const data = localStorage.getItem(`userConfig:${VERSION}`);
36
+ return data ? JSON.parse(data) : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ // Migration from v1 to v2
43
+ function migrate() {
44
+ try {
45
+ const v1 = localStorage.getItem("userConfig:v1");
46
+ if (v1) {
47
+ const old = JSON.parse(v1);
48
+ saveConfig({ theme: old.darkMode ? "dark" : "light", language: old.lang });
49
+ localStorage.removeItem("userConfig:v1");
50
+ }
51
+ } catch {}
52
+ }
53
+ ```
54
+
55
+ **Store minimal fields from server responses:**
56
+
57
+ ```typescript
58
+ // User object has 20+ fields, only store what UI needs
59
+ function cachePrefs(user: FullUser) {
60
+ try {
61
+ localStorage.setItem(
62
+ "prefs:v1",
63
+ JSON.stringify({
64
+ theme: user.preferences.theme,
65
+ notifications: user.preferences.notifications,
66
+ }),
67
+ );
68
+ } catch {}
69
+ }
70
+ ```
71
+
72
+ **Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
73
+
74
+ **Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Use Passive Event Listeners for Scrolling Performance
3
+ impact: MEDIUM
4
+ impactDescription: eliminates scroll delay caused by event listeners
5
+ tags: client, event-listeners, scrolling, performance, touch, wheel
6
+ ---
7
+
8
+ ## Use Passive Event Listeners for Scrolling Performance
9
+
10
+ Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ useEffect(() => {
16
+ const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX);
17
+ const handleWheel = (e: WheelEvent) => console.log(e.deltaY);
18
+
19
+ document.addEventListener("touchstart", handleTouch);
20
+ document.addEventListener("wheel", handleWheel);
21
+
22
+ return () => {
23
+ document.removeEventListener("touchstart", handleTouch);
24
+ document.removeEventListener("wheel", handleWheel);
25
+ };
26
+ }, []);
27
+ ```
28
+
29
+ **Correct:**
30
+
31
+ ```typescript
32
+ useEffect(() => {
33
+ const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX);
34
+ const handleWheel = (e: WheelEvent) => console.log(e.deltaY);
35
+
36
+ document.addEventListener("touchstart", handleTouch, { passive: true });
37
+ document.addEventListener("wheel", handleWheel, { passive: true });
38
+
39
+ return () => {
40
+ document.removeEventListener("touchstart", handleTouch);
41
+ document.removeEventListener("wheel", handleWheel);
42
+ };
43
+ }, []);
44
+ ```
45
+
46
+ **Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.
47
+
48
+ **Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Avoid Layout Thrashing
3
+ impact: MEDIUM
4
+ impactDescription: prevents forced synchronous layouts and reduces performance bottlenecks
5
+ tags: javascript, dom, css, performance, reflow, layout-thrashing
6
+ ---
7
+
8
+ ## Avoid Layout Thrashing
9
+
10
+ Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
11
+
12
+ **This is OK (browser batches style changes):**
13
+
14
+ ```typescript
15
+ function updateElementStyles(element: HTMLElement) {
16
+ // Each line invalidates style, but browser batches the recalculation
17
+ element.style.width = "100px";
18
+ element.style.height = "200px";
19
+ element.style.backgroundColor = "blue";
20
+ element.style.border = "1px solid black";
21
+ }
22
+ ```
23
+
24
+ **Incorrect (interleaved reads and writes force reflows):**
25
+
26
+ ```typescript
27
+ function layoutThrashing(element: HTMLElement) {
28
+ element.style.width = "100px";
29
+ const width = element.offsetWidth; // Forces reflow
30
+ element.style.height = "200px";
31
+ const height = element.offsetHeight; // Forces another reflow
32
+ }
33
+ ```
34
+
35
+ **Correct (batch writes, then read once):**
36
+
37
+ ```typescript
38
+ function updateElementStyles(element: HTMLElement) {
39
+ // Batch all writes together
40
+ element.style.width = "100px";
41
+ element.style.height = "200px";
42
+ element.style.backgroundColor = "blue";
43
+ element.style.border = "1px solid black";
44
+
45
+ // Read after all writes are done (single reflow)
46
+ const { width, height } = element.getBoundingClientRect();
47
+ }
48
+ ```
49
+
50
+ **Correct (batch reads, then writes):**
51
+
52
+ ```typescript
53
+ function avoidThrashing(element: HTMLElement) {
54
+ // Read phase - all layout queries first
55
+ const rect1 = element.getBoundingClientRect();
56
+ const offsetWidth = element.offsetWidth;
57
+ const offsetHeight = element.offsetHeight;
58
+
59
+ // Write phase - all style changes after
60
+ element.style.width = "100px";
61
+ element.style.height = "200px";
62
+ }
63
+ ```
64
+
65
+ **Better: use CSS classes**
66
+
67
+ ```css
68
+ .highlighted-box {
69
+ width: 100px;
70
+ height: 200px;
71
+ background-color: blue;
72
+ border: 1px solid black;
73
+ }
74
+ ```
75
+
76
+ ```typescript
77
+ function updateElementStyles(element: HTMLElement) {
78
+ element.classList.add("highlighted-box");
79
+
80
+ const { width, height } = element.getBoundingClientRect();
81
+ }
82
+ ```
83
+
84
+ **React example:**
85
+
86
+ ```tsx
87
+ // Incorrect: interleaving style changes with layout queries
88
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
89
+ const ref = useRef<HTMLDivElement>(null);
90
+
91
+ useEffect(() => {
92
+ if (ref.current && isHighlighted) {
93
+ ref.current.style.width = "100px";
94
+ const width = ref.current.offsetWidth; // Forces layout
95
+ ref.current.style.height = "200px";
96
+ }
97
+ }, [isHighlighted]);
98
+
99
+ return <div ref={ref}>Content</div>;
100
+ }
101
+
102
+ // Correct: toggle class
103
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
104
+ return <div className={isHighlighted ? "highlighted-box" : ""}>Content</div>;
105
+ }
106
+ ```
107
+
108
+ Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
109
+
110
+ See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Cache Repeated Function Calls
3
+ impact: MEDIUM
4
+ impactDescription: avoid redundant computation
5
+ tags: javascript, cache, memoization, performance
6
+ ---
7
+
8
+ ## Cache Repeated Function Calls
9
+
10
+ Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
11
+
12
+ **Incorrect (redundant computation):**
13
+
14
+ ```typescript
15
+ function ProjectList({ projects }: { projects: Project[] }) {
16
+ return (
17
+ <div>
18
+ {projects.map(project => {
19
+ // slugify() called 100+ times for same project names
20
+ const slug = slugify(project.name)
21
+
22
+ return <ProjectCard key={project.id} slug={slug} />
23
+ })}
24
+ </div>
25
+ )
26
+ }
27
+ ```
28
+
29
+ **Correct (cached results):**
30
+
31
+ ```typescript
32
+ // Module-level cache
33
+ const slugifyCache = new Map<string, string>()
34
+
35
+ function cachedSlugify(text: string): string {
36
+ if (slugifyCache.has(text)) {
37
+ return slugifyCache.get(text)!
38
+ }
39
+ const result = slugify(text)
40
+ slugifyCache.set(text, result)
41
+ return result
42
+ }
43
+
44
+ function ProjectList({ projects }: { projects: Project[] }) {
45
+ return (
46
+ <div>
47
+ {projects.map(project => {
48
+ // Computed only once per unique project name
49
+ const slug = cachedSlugify(project.name)
50
+
51
+ return <ProjectCard key={project.id} slug={slug} />
52
+ })}
53
+ </div>
54
+ )
55
+ }
56
+ ```
57
+
58
+ Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: Cache Property Access in Loops
3
+ impact: LOW-MEDIUM
4
+ impactDescription: reduces lookups
5
+ tags: javascript, loops, optimization, caching
6
+ ---
7
+
8
+ ## Cache Property Access in Loops
9
+
10
+ Cache object property lookups in hot paths.
11
+
12
+ **Incorrect (3 lookups × N iterations):**
13
+
14
+ ```typescript
15
+ for (let i = 0; i < arr.length; i++) {
16
+ process(obj.config.settings.value);
17
+ }
18
+ ```
19
+
20
+ **Correct (1 lookup total):**
21
+
22
+ ```typescript
23
+ const value = obj.config.settings.value;
24
+ const len = arr.length;
25
+ for (let i = 0; i < len; i++) {
26
+ process(value);
27
+ }
28
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ title: Cache Storage API Calls
3
+ impact: LOW-MEDIUM
4
+ impactDescription: reduces expensive I/O
5
+ tags: javascript, localStorage, storage, caching, performance
6
+ ---
7
+
8
+ ## Cache Storage API Calls
9
+
10
+ `localStorage` and `sessionStorage` are synchronous. Cache repeated reads in memory when they cause a measurable cost.
11
+
12
+ **Incorrect (reads storage on every call):**
13
+
14
+ ```typescript
15
+ function getTheme() {
16
+ return localStorage.getItem("theme") ?? "light";
17
+ }
18
+ // Called 10 times = 10 storage reads
19
+ ```
20
+
21
+ **Correct (Map cache):**
22
+
23
+ ```typescript
24
+ const storageCache = new Map<string, string | null>();
25
+
26
+ function getLocalStorage(key: string) {
27
+ if (!storageCache.has(key)) {
28
+ storageCache.set(key, localStorage.getItem(key));
29
+ }
30
+ return storageCache.get(key);
31
+ }
32
+
33
+ function setLocalStorage(key: string, value: string) {
34
+ localStorage.setItem(key, value);
35
+ storageCache.set(key, value); // keep cache in sync
36
+ }
37
+ ```
38
+
39
+ Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
40
+
41
+ **Important (invalidate on external changes):**
42
+
43
+ If storage can change in another tab, invalidate the cache:
44
+
45
+ ```typescript
46
+ window.addEventListener("storage", (e) => {
47
+ if (e.key === null) storageCache.clear();
48
+ else storageCache.delete(e.key);
49
+ });
50
+
51
+ document.addEventListener("visibilitychange", () => {
52
+ if (document.visibilityState === "visible") {
53
+ storageCache.clear();
54
+ }
55
+ });
56
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: Combine Multiple Array Iterations
3
+ impact: LOW-MEDIUM
4
+ impactDescription: reduces iterations
5
+ tags: javascript, arrays, loops, performance
6
+ ---
7
+
8
+ ## Combine Multiple Array Iterations
9
+
10
+ Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
11
+
12
+ **Incorrect (3 iterations):**
13
+
14
+ ```typescript
15
+ const admins = users.filter((u) => u.isAdmin);
16
+ const testers = users.filter((u) => u.isTester);
17
+ const inactive = users.filter((u) => !u.isActive);
18
+ ```
19
+
20
+ **Correct (1 iteration):**
21
+
22
+ ```typescript
23
+ const admins: User[] = [];
24
+ const testers: User[] = [];
25
+ const inactive: User[] = [];
26
+
27
+ for (const user of users) {
28
+ if (user.isAdmin) admins.push(user);
29
+ if (user.isTester) testers.push(user);
30
+ if (!user.isActive) inactive.push(user);
31
+ }
32
+ ```