Skip to content
Merged
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
2 changes: 2 additions & 0 deletions apps/penpal/ERD.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ see-also:
- <a id="E-PENPAL-SIDEBAR-RESIZE"></a>**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)

- <a id="E-PENPAL-VIEW-MARGINS"></a>**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)
- <a id="E-PENPAL-FRONTMATTER-STRIP"></a>**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)

Expand Down
2 changes: 2 additions & 0 deletions apps/penpal/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ Global views aggregate content across all projects. They appear as top-level ite

- <a id="P-PENPAL-RENDER"></a>**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.

- <a id="P-PENPAL-VIEW-MARGINS"></a>**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.

- <a id="P-PENPAL-GFM"></a>**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).

- <a id="P-PENPAL-FRONTMATTER"></a>**P-PENPAL-FRONTMATTER**: YAML/TOML frontmatter is stripped from the rendered output so users see only the document content.
Expand Down
1 change: 1 addition & 0 deletions apps/penpal/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions apps/penpal/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion apps/penpal/frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
38 changes: 38 additions & 0 deletions apps/penpal/frontend/src/test/file-view-layout.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.