Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 25 additions & 4 deletions apps/app/bundle-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
"them when a change wins headroom. Raising one is a deliberate decision",
"that needs a reason in the pull request, not a routine edit.",
"",
"There is deliberately no per-chunk limit. Parse and compile cost scales",
"with the total bytes on the boot path, not with how they are divided, so",
"splitting one boot chunk into two would satisfy a per-chunk cap while",
"making the page slightly slower for the extra request.",
"The workspace checkout display chunk has a separate limit because it was",
"the shared owner of the prompt, Markdown, and diff renderers. The route",
"package check keeps those features behind their dynamic boundaries.",
"",
"forbiddenBootPackages is the more important half. These packages are all",
"large and all needed only after a user action (open a diff, focus the",
Expand All @@ -27,6 +26,7 @@
],
"maxBootBytes": 1707047,
"maxBootBrotliBytes": 445742,
"maxWorkspaceCheckoutDisplayChunkBytes": 512000,
"forbiddenBootPackages": [
"@pierre/diffs",
"@pierre/theming",
Expand All @@ -48,5 +48,26 @@
"prosemirror-view",
"rehype-katex",
"shiki"
],
"forbiddenWorkspaceRoutePackages": [
"@pierre/diffs",
"@pierre/theming",
"@shikijs/core",
"@shikijs/engine-javascript",
"@shikijs/engine-oniguruma",
"@shikijs/langs",
"@shikijs/vscode-textmate",
"@tiptap/core",
"@tiptap/pm",
"@tiptap/react",
"@tiptap/starter-kit",
"cytoscape",
"katex",
"oniguruma-to-es",
"prosemirror-model",
"prosemirror-state",
"prosemirror-view",
"rehype-katex",
"shiki"
]
}
114 changes: 82 additions & 32 deletions apps/app/scripts/check-bundle-budget.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,54 +32,104 @@ const budget = JSON.parse(fs.readFileSync(budgetPath, "utf8"));

const kb = (n) => `${(n / 1024).toFixed(1)} KB`;
const failures = [];
const minPrecompressBytes = 1024;

// A boot chunk with no .br file would otherwise weigh zero against the
// compressed budget, so an unrun precompression step could hide real growth.
// Treat it as an error rather than guessing a size.
const missingBrotli = [];
let bootBytes = 0;
let bootBrotliBytes = 0;
for (const chunk of stats.bootChunks) {
bootBytes += chunk.bytes;
const brotliPath = path.join(distDir, `${chunk.fileName}.br`);
if (fs.existsSync(brotliPath)) {
bootBrotliBytes += fs.statSync(brotliPath).size;
} else {
missingBrotli.push(chunk.fileName);
// A chunk with no .br file would otherwise weigh zero against the compressed
// budget. The precompressor deliberately leaves files below 1 KB untouched,
// so count their full size and fail only when a larger compressed file is gone.
const missingBrotli = new Set();
const measureChunks = (chunks) => {
let bytes = 0;
let brotliBytes = 0;
for (const chunk of chunks) {
bytes += chunk.bytes;
const brotliPath = path.join(distDir, `${chunk.fileName}.br`);
if (fs.existsSync(brotliPath)) {
brotliBytes += fs.statSync(brotliPath).size;
} else if (chunk.bytes < minPrecompressBytes) {
brotliBytes += chunk.bytes;
} else {
missingBrotli.add(chunk.fileName);
}
}
}
return { bytes, brotliBytes };
};

const forbidden = new Set(budget.forbiddenBootPackages);
const offenders = new Map();
for (const chunk of stats.bootChunks) {
for (const pkg of chunk.packages) {
if (!forbidden.has(pkg)) continue;
if (!offenders.has(pkg)) offenders.set(pkg, []);
offenders.get(pkg).push(chunk.fileName);
const bootPayload = measureChunks(stats.bootChunks);
const workspaceRoutePayload = measureChunks(stats.workspaceRouteChunks);

const findForbiddenPackages = (chunks, forbiddenPackages) => {
const forbidden = new Set(forbiddenPackages);
const offenders = new Map();
for (const chunk of chunks) {
for (const pkg of chunk.packages) {
if (!forbidden.has(pkg)) continue;
if (!offenders.has(pkg)) offenders.set(pkg, []);
offenders.get(pkg).push(chunk.fileName);
}
}
}
return offenders;
};

const bootOffenders = findForbiddenPackages(
stats.bootChunks,
budget.forbiddenBootPackages,
);
const workspaceRouteOffenders = findForbiddenPackages(
stats.workspaceRouteChunks,
budget.forbiddenWorkspaceRoutePackages,
);

console.log(`boot payload: ${kb(bootBytes)} raw / ${kb(bootBrotliBytes)} brotli`);
console.log(` budget: ${kb(budget.maxBootBytes)} raw / ${kb(budget.maxBootBrotliBytes)} brotli`);
console.log(
`boot payload: ${kb(bootPayload.bytes)} raw / ${kb(bootPayload.brotliBytes)} brotli`,
);
console.log(
` budget: ${kb(budget.maxBootBytes)} raw / ${kb(budget.maxBootBrotliBytes)} brotli`,
);
console.log(` chunks: ${stats.bootChunks.length}`);
console.log(
`workspace route: ${kb(workspaceRoutePayload.bytes)} raw / ${kb(workspaceRoutePayload.brotliBytes)} brotli`,
);
console.log(` chunks: ${stats.workspaceRouteChunks.length}`);
console.log(
`checkout chunk: ${kb(stats.workspaceCheckoutDisplayChunk.bytes)} raw`,
);
console.log(
` budget: ${kb(budget.maxWorkspaceCheckoutDisplayChunkBytes)} raw`,
);

if (missingBrotli.length > 0) {
if (missingBrotli.size > 0) {
failures.push(
`${missingBrotli.size} measured chunk(s) have no .br file, so the compressed total is understated: ${[...missingBrotli].join(", ")}. Run scripts/precompress-app-dist.mjs.`,
);
}
if (bootPayload.bytes > budget.maxBootBytes) {
failures.push(
`${missingBrotli.length} boot chunk(s) have no .br file, so the compressed total is understated: ${missingBrotli.join(", ")}. Run scripts/precompress-app-dist.mjs.`,
`boot payload is ${kb(bootPayload.bytes)}, over the ${kb(budget.maxBootBytes)} raw budget by ${kb(bootPayload.bytes - budget.maxBootBytes)}.`,
);
}
if (bootBytes > budget.maxBootBytes) {
if (bootPayload.brotliBytes > budget.maxBootBrotliBytes) {
failures.push(
`boot payload is ${kb(bootBytes)}, over the ${kb(budget.maxBootBytes)} raw budget by ${kb(bootBytes - budget.maxBootBytes)}.`,
`boot payload is ${kb(bootPayload.brotliBytes)} brotli, over the ${kb(budget.maxBootBrotliBytes)} budget by ${kb(bootPayload.brotliBytes - budget.maxBootBrotliBytes)}.`,
);
}
if (bootBrotliBytes > budget.maxBootBrotliBytes) {
if (
stats.workspaceCheckoutDisplayChunk.bytes >
budget.maxWorkspaceCheckoutDisplayChunkBytes
) {
failures.push(
`boot payload is ${kb(bootBrotliBytes)} brotli, over the ${kb(budget.maxBootBrotliBytes)} budget by ${kb(bootBrotliBytes - budget.maxBootBrotliBytes)}.`,
`workspace checkout display chunk is ${kb(stats.workspaceCheckoutDisplayChunk.bytes)}, over the ${kb(budget.maxWorkspaceCheckoutDisplayChunkBytes)} raw budget.`,
);
}
for (const [pkg, chunks] of offenders) {
failures.push(`${pkg} is in the boot payload (${chunks.join(", ")}). It must load on demand.`);
for (const [pkg, chunks] of bootOffenders) {
failures.push(
`${pkg} is in the boot payload (${chunks.join(", ")}). It must load on demand.`,
);
}
for (const [pkg, chunks] of workspaceRouteOffenders) {
failures.push(
`${pkg} is in the workspace route preload (${chunks.join(", ")}). It must load on demand.`,
);
}

if (failures.length > 0) {
Expand Down
8 changes: 1 addition & 7 deletions apps/app/src/components/git-diff/GitDiffCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ export type {
RequestDiffFileContents,
} from "./GitDiffCardBody";

export const GIT_DIFF_VIEW_BASE_OPTIONS = {
overflow: "scroll",
disableFileHeader: false,
// Reveal 30 unchanged lines per expand-up / expand-down click. Library
// default is 100 — too aggressive for our compact diff cards.
expansionLineCount: 30,
} as const;
export { GIT_DIFF_VIEW_BASE_OPTIONS } from "./git-diff-options";

export interface GitDiffCardProps {
fileDiff: ParsedGitDiffFile;
Expand Down
21 changes: 15 additions & 6 deletions apps/app/src/components/git-diff/GitDiffCardBody.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
type CSSProperties,
type RefCallback,
lazy,
Suspense,
useCallback,
useEffect,
useMemo,
Expand All @@ -13,7 +15,6 @@ import type {
SelectedLineRange,
SelectionSide,
} from "@pierre/diffs";
import { FileDiff as DiffView } from "@pierre/diffs/react";
import { useIntersectionObserver } from "usehooks-ts";
import { Button } from "@bb/shared-ui/button";
import { usePierreLineSelectionActions } from "./PierreLineSelectionActions.js";
Expand All @@ -33,6 +34,12 @@ import {
type ParsedGitDiffFile,
} from "./git-diff-parsing";

const LazyPierreDiffView = lazy(() =>
import("./PierreDiffView").then((module) => ({
default: module.PierreDiffView,
})),
);

/**
* One side of a diff file resolved for the card. `text` carries UTF-8 contents
* for `@pierre/diffs` context expansion; `image` carries a data URL the card
Expand Down Expand Up @@ -1174,11 +1181,13 @@ function GitDiffCardRawDiffBody({
onPointerUpCapture={lineSelectionActions.onPointerUpCapture}
>
<div className="w-full max-w-full" style={GIT_DIFF_CARD_VIEW_STYLE}>
<DiffView
fileDiff={fileDiff}
options={options}
selectedLines={lineSelectionActions.selectedRange}
/>
<Suspense fallback={<GitDiffCardBodySkeleton />}>
<LazyPierreDiffView
fileDiff={fileDiff}
options={options}
selectedLines={lineSelectionActions.selectedRange}
/>
</Suspense>
</div>
{lineSelectionActions.menu}
</div>
Expand Down
23 changes: 23 additions & 0 deletions apps/app/src/components/git-diff/PierreDiffView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { FileDiffOptions, SelectedLineRange } from "@pierre/diffs";
import { FileDiff } from "@pierre/diffs/react";
import type { ParsedGitDiffFile } from "./git-diff-parsing";

export interface PierreDiffViewProps {
fileDiff: ParsedGitDiffFile;
options: FileDiffOptions<undefined>;
selectedLines: SelectedLineRange | null;
}

export function PierreDiffView({
fileDiff,
options,
selectedLines,
}: PierreDiffViewProps) {
return (
<FileDiff
fileDiff={fileDiff}
options={options}
selectedLines={selectedLines}
/>
);
}
7 changes: 7 additions & 0 deletions apps/app/src/components/git-diff/git-diff-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const GIT_DIFF_VIEW_BASE_OPTIONS = {
overflow: "scroll",
disableFileHeader: false,
// Reveal 30 unchanged lines per expand-up or expand-down action. The library
// default of 100 lines is too large for compact diff cards.
expansionLineCount: 30,
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import {
INERT_TYPEAHEAD_COMMAND_CONFIG,
PromptBoxInternal,
} from "./PromptBoxInternal";
} from "./PromptBoxInternalImpl";

const testState = vi.hoisted(() => ({
calls: [] as string[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
INERT_TYPEAHEAD_COMMAND_CONFIG,
PromptBoxInternal,
} from "./PromptBoxInternal";
} from "./PromptBoxInternalImpl";

// ProseMirror waits this long before it replays a swallowed iOS Enter.
const IOS_ENTER_REPLAY_MS = 200;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
type PromptBoxHandle,
type PromptVoiceConfig,
type TypeaheadConfig,
} from "./PromptBoxInternal";
} from "./PromptBoxInternalImpl";
import type {
PromptMentionSuggestion,
ProviderCommandSuggestion,
Expand Down
Loading
Loading