Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) by jackwener · Pull Request #876 · apache/maka · GitHub
Skip to content

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B) - #876

Merged
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition
Jul 13, 2026
Merged

refactor(renderer): app-shell state-cluster decomposition — 4/5 blades (Round B)#876
jackwener merged 6 commits into
mainfrom
refactor/app-shell-decomposition

Conversation

@jackwener

Copy link
Copy Markdown
Member

Round B (flagship) of notes/frontend-simplification-map-2026-07-13.md. app-shell.tsx 1733 → 1666 lines, 31 → 19 useState, zero behavior change; one extraction per commit, full gate between each.

Blades

  1. use-pending-action-registry — the dedup blade: ONE generic useKeyedPendingRegistry replaces the four hand-rolled keyed pending sets (turn actions + timers, session-row actions, permission-mode changes, session-model changes). keysRef stays a stable Set so factories and unmount cleanup are byte-identical. Contracts sticky-model / status-presentation / row-actions-fail-soft re-pinned to the registry form.
  2. use-project-context — appInfo, branchList/pending, recentProjectPaths, projectPicker refs + project-actions wiring.
  3. use-module-data — skills / managedSkillSources / bundledSkillCatalog / planReminders + both action factories.
  4. use-shell-connections (partial) — connections/defaultConnection + connectionsEqual + refresh. theme/userLabel/defaultPermissionMode stayed: default-permission-mode contract pins them to app-shell.tsx specifically, and the theme setters have multiple app-shell writers — moving them is setter-injection churn with no net simplification.
  5. use-shell-navigation — SKIPPED (documented in the map): its anchors are source-pinned to app-shell.tsx, and the movable bridge callbacks carry PR-FE-BUG-HUNT-0 identity-stability semantics with zero gate coverage — an extraction slip would silently regress search-during-stream. A state-only move nets ≈0 lines.

The map's <900 target is recorded as structurally unreachable via state-cluster blades alone (remaining bulk = ~210 L derived-value memos + ~340 L JSX return; separate scope).

Gates

Every commit + post-merge-with-main (this branch includes merged Round C/D-1): desktop 2397/2397 · ui 125/125 · typecheck · check-dead-css · knip ×2 = 0 · alignment auditor clean on all 9 fixtures · CDP branch-vs-baseline pixel captures: module-skills byte-identical; two known-environmental diffs (live git-branch label in composer; overlay-scrollbar fade ≤25/255) verified non-regressions. Implemented by an opus worktree agent; map conflict with Rounds C/D-1 resolved keeping both records.

…ding-action sets
Replace the four hand-rolled keyed de-dup registries (turn-footer actions
with state+timers, session-row actions, per-session permission-mode and
model changes) with one generic useKeyedPendingRegistry. Only the
turn-footer instance opts into the React-visible state mirror + 5s
auto-clear timers; the other three stay ref-only and keep clearing in
their action factories' finally blocks. keysRef stays a stable Set so the
action factories and the unmount cleanup effect operate on the same object
— zero behavior change. Re-pin the source-slice contracts (sticky-model,
permission-mode presentation, row-actions fail-soft) to the registry form.
Blade 1 of Round B app-shell decomposition.
Move the workspace / project-picker cluster (appInfo + git status, branch
list + pending flag, recent-workspace history, project-picker pending state
and its dedup refs) plus the createAppShellProjectActions wiring into a
dedicated hook. Seeds still come from the persisted composer defaults;
the picker refs are returned for the bootstrap unmount cleanup. Pure move,
zero behavior change. Add the two new hook files to the renderer shell
source allowlist so combined-source contracts keep seeing the moved decls.
Blade 3 of Round B app-shell decomposition.
Move the two sidebar-module data clusters — skills / managed sources /
bundled catalog and plan reminders — plus their createAppShellSkillActions
and createAppShellPlanActions wiring into one hook. The surface-active
predicates are injected so mutation error toasts stay scoped to the
foreground module, and the plan getter still reads the latest reminders per
call. Pure move, zero behavior change.
Blade 4 of Round B app-shell decomposition.
Move the LLM-connection cluster — the connection list, default-connection
slug, connectionsEqual dedup, refreshConnections, and handleConnectionEvent
— into a dedicated hook. setConnections/setDefaultConnection are returned so
the onboarding-snapshot seed (which stays in AppShell to also seed sessions)
can prime them. Pure move, zero behavior change.
Partial vs the map's blade 5: themePref/themePalette, userLabel and
defaultPermissionMode stay in app-shell.tsx — the default-permission-mode
contract pins the defaultPermissionMode useState + refreshShellSettings +
closeSettings mirror to app-shell.tsx specifically, and the theme setters
have multiple app-shell writers (visual-smoke, settings-overlay onChange),
so moving them would require setter injection with no net simplification.
Blade 5 of Round B app-shell decomposition.
…pped
app-shell.tsx 1733 → 1666; blades 1/3/4/5 extracted (pending-registry,
project-context, module-data, shell-connections); blade 2 (navigation)
skipped as disproportionately risky (anchors pinned to app-shell, bridge
callbacks carry untested runtime identity semantics). Documents why the
< 900 target is unreachable through state-cluster blades alone.
# Conflicts:
#	notes/frontend-simplification-map-2026-07-13.md
@jackwener
jackwener merged commit fe978ea into mainJul 13, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/app-shell-decomposition branch July 14, 2026 05:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@jackwener