Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
03d6ebe
fix(desktop,ui): restage a selected message's quotes and attachments …
ggbdpq Sep 13, 2026
edf1f0c
chore(desktop): refresh the renderer architecture ledger
ggbdpq Sep 13, 2026
88dc60e
refactor(desktop,ui): move revision staged-context helpers into @maka/ui
ggbdpq Sep 13, 2026
d93924a
refactor(desktop,ui): tighten the revision staged-context call sites
ggbdpq Sep 13, 2026
c4d7065
refactor(desktop,ui): move the revision lifecycle into @maka/ui
ggbdpq Sep 14, 2026
0a5af05
chore: retrigger CI
ggbdpq Sep 14, 2026
ed3eee4
Merge remote-tracking branch 'upstream/main' into fix/desktop-revisio…
ggbdpq Sep 15, 2026
f49eb88
fix(desktop): keep the revision assembler at one @maka/ui import
ggbdpq Sep 15, 2026
c240950
refactor(desktop,ui): single @maka/ui declaration and module doc note
ggbdpq Sep 15, 2026
e156414
Merge remote-tracking branch 'upstream/main' into fix/desktop-revisio…
ggbdpq Sep 15, 2026
d53b28f
chore(desktop): sanction @maka/ui edges for legacy shell importers
ggbdpq Sep 17, 2026
ceccd38
fix(desktop,ui): make the revision restage honest about what can follow
ggbdpq Sep 17, 2026
ec76e4a
Merge remote-tracking branch 'upstream/main' into fix/desktop-revisio…
ggbdpq Sep 17, 2026
a5b73dc
fix(desktop): adapt the revision lifecycle to the shell's message upd…
ggbdpq Sep 17, 2026
0629e67
chore: retrigger CI after a flaky storybook visual smoke run
ggbdpq Sep 17, 2026
ddb5d43
Merge remote-tracking branch 'upstream/main' into fix/desktop-revisio…
ggbdpq Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
"legacyAppShell": {
"files": {
"src/renderer/app-shell-chat-actions.ts": {
"importDeclarations": 7,
"importDeclarations": 6,
"bridgePaths": {
"window.maka.newTasks.create": 1,
"window.maka.sessions.remove": 1,
Expand All @@ -336,11 +336,11 @@
"@maka/core/session-name": 1,
"@maka/ui": 1
},
"importSpecifiers": 10,
"importSpecifiers": 9,
"nonTriviaTokens": 3603
},
"src/renderer/app-shell-chrome-actions.tsx": {
"importDeclarations": 4,
"importDeclarations": 2,
"bridgePaths": {},
"environmentCapabilities": {},
"hookCalls": {
Expand All @@ -356,7 +356,7 @@
"@maka/ui": 1,
"@maka/ui/icons": 1
},
"importSpecifiers": 7,
"importSpecifiers": 2,
"nonTriviaTokens": 408
},
"src/renderer/app-shell-command-actions.ts": {
Expand Down Expand Up @@ -506,7 +506,7 @@
"nonTriviaTokens": 3677
},
"src/renderer/app-shell-overlays.tsx": {
"importDeclarations": 5,
"importDeclarations": 4,
"bridgePaths": {},
"environmentCapabilities": {
"window.addEventListener": 1,
Expand All @@ -532,7 +532,7 @@
"@maka/ui": 1,
"react": 1
},
"importSpecifiers": 8,
"importSpecifiers": 7,
"nonTriviaTokens": 860
},
"src/renderer/app-shell-project-actions.ts": {
Expand Down Expand Up @@ -566,7 +566,7 @@
"nonTriviaTokens": 2284
},
"src/renderer/app-shell-revision-actions.ts": {
"importDeclarations": 4,
"importDeclarations": 3,
"bridgePaths": {
"window.maka.sessions.abandonSessionCopy": 2,
"window.maka.sessions.reviseBeforeTurn": 1
Expand All @@ -584,13 +584,13 @@
"./platform/desktop/session-message-settlement.js": 1,
"./session-copy-attempt.js": 1,
"./session-workspace-errors.js": 1,
"@maka/core/session": 1
"@maka/ui": 1
},
"importSpecifiers": 8,
"nonTriviaTokens": 2155
"importSpecifiers": 4,
"nonTriviaTokens": 537
},
"src/renderer/app-shell-session-events.ts": {
"importDeclarations": 2,
"importDeclarations": 1,
"bridgePaths": {},
"environmentCapabilities": {
"window.setTimeout": 1
Expand All @@ -609,7 +609,7 @@
"./model-connection-errors.js": 1,
"@maka/ui": 1
},
"importSpecifiers": 7,
"importSpecifiers": 1,
"nonTriviaTokens": 2687
},
"src/renderer/app-shell-session-start-actions.ts": {
Expand Down Expand Up @@ -689,7 +689,7 @@
"nonTriviaTokens": 620
},
"src/renderer/app-shell-turn-view-model.ts": {
"importDeclarations": 6,
"importDeclarations": 5,
"bridgePaths": {},
"environmentCapabilities": {},
"hookCalls": {
Expand All @@ -708,11 +708,11 @@
"@maka/ui": 1,
"react": 1
},
"importSpecifiers": 10,
"importSpecifiers": 7,
"nonTriviaTokens": 1273
},
"src/renderer/app-shell.tsx": {
"importDeclarations": 64,
"importDeclarations": 63,
"bridgePaths": {
"window.maka.attachments": 1,
"window.maka.attachments.readBytes": 1,
Expand Down Expand Up @@ -861,8 +861,8 @@
"@maka/ui": 1,
"react": 1
},
"importSpecifiers": 100,
"nonTriviaTokens": 13135
"importSpecifiers": 88,
"nonTriviaTokens": 13089
},
"src/renderer/use-app-shell-composer-quotes.ts": {
"importDeclarations": 0,
Expand All @@ -879,7 +879,7 @@
"nonTriviaTokens": 24
},
"src/renderer/use-app-shell-session-list.ts": {
"importDeclarations": 6,
"importDeclarations": 5,
"bridgePaths": {
"window.maka.sessions.list": 1
},
Expand All @@ -903,7 +903,7 @@
"@maka/ui": 1,
"react": 1
},
"importSpecifiers": 9,
"importSpecifiers": 8,
"nonTriviaTokens": 486
},
"src/renderer/use-app-shell-session-ui-reads.ts": {
Expand Down
25 changes: 24 additions & 1 deletion apps/desktop/scripts/check-renderer-architecture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const RENDERER_VITE_CONFIG = 'vite.config.ts';
const RENDERER_BUILD_SCRIPT =
'vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs';
const DESKTOP_SELF_PREFIX = '@maka/desktop/';
// The package renderer ownership is migrating into. Shell debt is defined to
// shrink by moving onto it, so depending on the destination is the opposite
// of debt and its edges are sanctioned for shell importers.
const MIGRATION_TARGET_PACKAGE = '@maka/ui';
const CAPABILITY_DEBT_METRICS = [
'actionFactories',
'bridgePaths',
Expand Down Expand Up @@ -3101,9 +3105,28 @@ function withoutSanctionedDependencies(desktopRoot, section, importerPath, depen
return filtered;
}

function isMigrationTargetPackageSpecifier(dependency) {
const specifier = dependency.split(/[?#]/u, 1)[0];
return (
specifier === MIGRATION_TARGET_PACKAGE ||
specifier.startsWith(`${MIGRATION_TARGET_PACKAGE}/`)
);
}

function isSanctionedDependencyTarget(desktopRoot, section, importerPath, dependency) {
const target = resolveDependency(desktopRoot, resolve(desktopRoot, importerPath), dependency);
if (!target) return false;
if (!target) {
// Bare package specifiers resolve to nothing inside the desktop tree.
// The migration destination is the one free among them: a shell importer
// depending on @maka/ui sheds ownership the shell is defined to lose,
// the same way validated copy catalogs take bare-package imports for
// free. Root entries stay fully priced: they are meant to become thin
// mounts.
return (
(section === 'legacyAppShell' || section === 'legacyAppShellClosure') &&
isMigrationTargetPackageSpecifier(dependency)
);
}
const targetRelative = normalizePath(relative(desktopRoot, target));
if (isValidatedCopyCatalog(desktopRoot, targetRelative)) return true;
// Root entries are meant to become thin mounts; only catalogs are free for them.
Expand Down
79 changes: 79 additions & 0 deletions apps/desktop/scripts/check-renderer-architecture.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3454,6 +3454,85 @@ describe('renderer architecture base-tree derivation (git fixtures)', () => {
});
});

it('sanctions a shell file migrating onto @maka/ui relative to the derived base tree', async () => {
// Only files named app-shell* enter the legacyAppShell ledger section,
// and every section file needs an ownership entry to pass validation.
const LEGACY_SHELL_WIDGET = 'src/renderer/app-shell-widget.ts';
const seed = architectureConfig({
rootDebt: { [RENDERER_ENTRY_PATH]: emptyDebt() },
ownership: [
{
capability: 'fixture-root',
targetZone: 'bootstrap',
legacyPaths: [RENDERER_ENTRY_PATH],
},
{
capability: 'fixture-shell-widget',
targetZone: 'shell',
legacyPaths: [LEGACY_SHELL_WIDGET],
},
],
});
await withGitFixture(async (fixture) => {
// The base file carries more debt than the head ever will: the migration
// edge must be the only delta under test, so every priced metric shrinks.
await fixture.writeFiles({
[LEGACY_SHELL_WIDGET]: `
import { existsSync } from 'node:fs';
import { join } from 'node:path';

const widgetSlots = ['header', 'body', 'footer'];
const resolveWidgetPath = (root: string, name: string) =>
existsSync(join(root, name)) ? join(root, name) : root;

export const legacyWidget = {
name: 'legacy-widget',
slots: widgetSlots,
resolve: resolveWidgetPath,
};
`,
});
await fixture.writeLedger(seed);
const base = fixture.commit('base');

await fixture.writeFiles({
[LEGACY_SHELL_WIDGET]: `
import { revisionStage } from '@maka/ui';

export const legacyWidget = { name: 'legacy-widget', stage: revisionStage };
`,
});
await fixture.writeLedger(seed);
fixture.commit('migrate a legacy shell file onto @maka/ui');

for (const args of [['--base', base], ['--base', base, '--strict-base']]) {
assertPassed(fixture.runChecker(args), base, args.join(' '));
}
});
});

it('keeps pricing an @maka/ui edge gained by a root debt entry under --strict-base', async () => {
await withGitFixture(async (fixture) => {
await fixture.writeLedger();
const base = fixture.commit('base');
await fixture.writeFiles({
[RENDERER_ENTRY_PATH]: `
import { revisionStage } from '@maka/ui';
export const main = revisionStage;
`,
});
await fixture.writeLedger();
fixture.commit('point the root entry at @maka/ui');

const result = fixture.runChecker(['--base', base, '--strict-base']);
assert.notEqual(result.status, 0);
assert.match(
result.stderr,
/^- src\/renderer\/main\.tsx: new dependency debt @maka\/ui/mu,
);
});
});

it('does not wedge on a base ledger that under-reports its own tree (#4250)', async () => {
await withGitFixture(async (fixture) => {
// The base ledger only knows one legacy file while the base *tree*
Expand Down
50 changes: 47 additions & 3 deletions apps/desktop/src/main/__tests__/app-shell-revision-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ function userMessage(turnId: string, text: string, extra: Record<string, unknown
function createActions(input: { messages: StoredMessage[] }) {
const drafts: unknown[] = [];
let composerText = '';
const staged: {
quotes: unknown[];
restoredQuotes: unknown[][];
clearedKeys: string[];
} = { quotes: [], restoredQuotes: [], clearedKeys: [] };
const revisionDraftRef: { current: unknown } = { current: null };
const actions = createAppShellRevisionActions({
uiLocale: 'en' as never,
Expand All @@ -56,6 +61,18 @@ function createActions(input: { messages: StoredMessage[] }) {
},
messages: input.messages,
hasPendingAttachments: () => false,
stagedContext: () => ({
quotes: staged.quotes,
attachments: [],
restoreQuotes: (_ownerKey: string, quotes: unknown[]) => {
staged.restoredQuotes.push(quotes);
staged.quotes.push(...quotes);
},
clearQuotes: (ownerKey: string) => {
staged.clearedKeys.push(ownerKey);
staged.quotes.length = 0;
},
}),
openSessionInChat: () => {},
refreshMessages: async () => true,
refreshSessions: async () => [],
Expand All @@ -70,7 +87,11 @@ function createActions(input: { messages: StoredMessage[] }) {
error: () => {},
},
} as never);
return Object.assign(actions, { drafts, composerState: { get text(): string { return composerText; } } });
return Object.assign(actions, {
drafts,
staged,
composerState: { get text(): string { return composerText; } },
});
}

describe('app-shell revision actions with structured context (#5109)', () => {
Expand Down Expand Up @@ -98,7 +119,7 @@ describe('app-shell revision actions with structured context (#5109)', () => {
assert.equal(h.composerState.text, 'plain follow-up');
});

it('rejects a source message that itself carries attachments', () => {
it('refuses editing a message that carries attachments (#5109 review)', () => {
const h = createActions({
messages: [
userMessage('turn-1', 'with image', {
Expand All @@ -117,6 +138,29 @@ describe('app-shell revision actions with structured context (#5109)', () => {

h.beginEditUserMessage('turn-1');

assert.equal(h.drafts.at(-1), undefined, 'attachment-bearing sources stay explicitly rejected');
assert.equal(
h.drafts.at(-1),
undefined,
'a revision copy excludes the revised turn, so no target-owned attachment rewrite exists to restage',
);
assert.equal(h.composerState.text, '', 'the composer stays untouched');
});

it('stages a source message quotes into the composer', () => {
const quote = { text: 'a large pasted excerpt', sourceTurnId: 'turn-0' };
const h = createActions({
messages: [userMessage('turn-1', 'explain this', { quotes: [quote] })],
});

h.beginEditUserMessage('turn-1');

const draft = h.drafts.at(-1) as { originalQuotes?: unknown[] } | undefined;
assert.ok(draft, 'a quote-carrying source message is editable now');
assert.deepEqual(draft?.originalQuotes, [quote]);
assert.deepEqual(
h.staged.restoredQuotes.at(-1),
[quote],
'the source quotes stage into the composer verbatim',
);
});
});
Loading