diff --git a/apps/penpal/ERD.md b/apps/penpal/ERD.md
index 3b8f96366..3b3677d8f 100644
--- a/apps/penpal/ERD.md
+++ b/apps/penpal/ERD.md
@@ -309,6 +309,8 @@ see-also:
- **E-PENPAL-SIDEBAR-RESIZE**: The left sidebar width is managed in `Layout.tsx` React state (`sidebarWidth`), initialized from `localStorage.getItem('sidebarWidth')` (default 240px). A 4px drag handle (`.sidebar-resize-handle`) on the right edge of the sidebar initiates mouse-tracking: `mousedown` captures `startX` and `startWidth`, `mousemove` computes `newWidth = startWidth + (clientX - startX)` clamped to [200, 700], `mouseup` persists to `localStorage`. The `.app` grid template uses an inline style `gridTemplateColumns: \`${sidebarWidth}px 1fr\`` to override the CSS default. The drag handle uses the same visual styling as the existing `.chat-resize-handle` (2px accent-colored indicator on hover).
← [P-PENPAL-SIDEBAR-RESIZE](PRODUCT.md#P-PENPAL-SIDEBAR-RESIZE)
+- **E-PENPAL-VIEW-MARGINS**: The `.file-main-scroll` container uses symmetric left and right padding (40px) and no `max-width` cap so that content expands to fill the available column width. The file-page-layout CSS grid separates the content column from the comments panel column with a resize handle; the visual margin between content text and comments panel equals the margin between the left sidebar border and the content text.
+ ← [P-PENPAL-VIEW-MARGINS](PRODUCT.md#P-PENPAL-VIEW-MARGINS)
- **E-PENPAL-FRONTMATTER-STRIP**: `markdown.StripFrontmatter()` checks for `---` prefix, finds the next `\n---` occurrence, and returns content after it with leading newlines trimmed. Applied in `handleRawFile` before serving content and in `publish/render.go` during publishing. The frontend renders the already-stripped content.
← [P-PENPAL-FRONTMATTER](PRODUCT.md#P-PENPAL-FRONTMATTER)
diff --git a/apps/penpal/PRODUCT.md b/apps/penpal/PRODUCT.md
index 72169c44a..b57dd314c 100644
--- a/apps/penpal/PRODUCT.md
+++ b/apps/penpal/PRODUCT.md
@@ -200,6 +200,8 @@ Global views aggregate content across all projects. They appear as top-level ite
- **P-PENPAL-RENDER**: Clicking a file opens a two-pane view: rendered markdown on the left, comments panel on the right, with a draggable divider.
+- **P-PENPAL-VIEW-MARGINS**: The file view maintains equal margins between the content and each side boundary (left sidebar and right comments panel). Content expands to fill available space between the margins.
+
- **P-PENPAL-GFM**: Markdown rendering supports GitHub Flavored Markdown: tables, task lists, strikethrough, autolinks, and syntax-highlighted code blocks with a dark color scheme (for fenced blocks with a language specifier).
- **P-PENPAL-FRONTMATTER**: YAML/TOML frontmatter is stripped from the rendered output so users see only the document content.
diff --git a/apps/penpal/TESTING.md b/apps/penpal/TESTING.md
index 4dabf4b60..a077af67b 100644
--- a/apps/penpal/TESTING.md
+++ b/apps/penpal/TESTING.md
@@ -100,6 +100,7 @@ see-also:
| File Handler (P-PENPAL-FILE-HANDLER) | — | — | api_manage_test.go | — |
| Source Disambiguation (P-PENPAL-SRC-DISAMBIG) | — | Layout.test.tsx | — | — |
| Home Label (P-PENPAL-HOME-LABEL) | — | Layout.test.tsx | — | — |
+| File View Margins (P-PENPAL-VIEW-MARGINS) | — | file-view-layout.test.ts | — | — |
## Known Coverage Gaps
diff --git a/apps/penpal/frontend/package.json b/apps/penpal/frontend/package.json
index ad9f4f13e..9be94a45a 100644
--- a/apps/penpal/frontend/package.json
+++ b/apps/penpal/frontend/package.json
@@ -35,6 +35,7 @@
"@testing-library/react": "^16.0.0",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
+ "@types/node": "^24.11.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/unist": "^3.0.3",
diff --git a/apps/penpal/frontend/src/index.css b/apps/penpal/frontend/src/index.css
index 74d402b5a..d08fddc11 100644
--- a/apps/penpal/frontend/src/index.css
+++ b/apps/penpal/frontend/src/index.css
@@ -888,7 +888,8 @@ a:hover { text-decoration: underline; }
opacity: 1;
}
.file-main { display: flex; flex-direction: column; overflow: hidden; }
-.file-main-scroll { padding: 0 40px 24px 40px; max-width: 900px; overflow-y: auto; flex: 1; }
+/* E-PENPAL-VIEW-MARGINS: symmetric padding, no max-width cap — content expands to fill column */
+.file-main-scroll { padding: 0 40px 24px 40px; overflow-y: auto; flex: 1; }
/* Sidebar floating card (TOC) */
.sidebar-card {
diff --git a/apps/penpal/frontend/src/test/file-view-layout.test.ts b/apps/penpal/frontend/src/test/file-view-layout.test.ts
new file mode 100644
index 000000000..bf7e53484
--- /dev/null
+++ b/apps/penpal/frontend/src/test/file-view-layout.test.ts
@@ -0,0 +1,38 @@
+import { describe, it, expect } from 'vitest';
+import { readFileSync } from 'node:fs';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+// E-PENPAL-VIEW-MARGINS: verify file view layout CSS enforces symmetric margins
+// and no max-width cap on the scroll container.
+describe('file view layout margins', () => {
+ const here = dirname(fileURLToPath(import.meta.url));
+ const css = readFileSync(resolve(here, '../index.css'), 'utf-8');
+
+ it('file-main-scroll has symmetric left and right padding', () => {
+ // Extract the .file-main-scroll rule
+ const match = css.match(/\.file-main-scroll\s*\{([^}]+)\}/);
+ expect(match).not.toBeNull();
+ const rule = match![1];
+
+ // Padding should include equal left and right values
+ const paddingMatch = rule.match(/padding:\s*([^;]+)/);
+ expect(paddingMatch).not.toBeNull();
+ const padding = paddingMatch![1].trim();
+
+ // Parse the padding shorthand — expect "0 Xpx Ypx Xpx" form where left === right
+ const parts = padding.split(/\s+/);
+ // 4-value shorthand: top right bottom left
+ expect(parts.length).toBe(4);
+ const rightPadding = parts[1];
+ const leftPadding = parts[3];
+ expect(rightPadding).toBe(leftPadding);
+ });
+
+ it('file-main-scroll does not have a max-width constraint', () => {
+ const match = css.match(/\.file-main-scroll\s*\{([^}]+)\}/);
+ expect(match).not.toBeNull();
+ const rule = match![1];
+ expect(rule).not.toMatch(/max-width/);
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a80b79c93..99ef4b462 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -129,6 +129,9 @@ importers:
'@types/mdast':
specifier: ^4.0.4
version: 4.0.4
+ '@types/node':
+ specifier: ^24.11.0
+ version: 24.11.0
'@types/react':
specifier: ^19.0.0
version: 19.2.14