diff --git a/Justfile b/Justfile index 93fe0fa..5afbdeb 100644 --- a/Justfile +++ b/Justfile @@ -604,7 +604,7 @@ mcp-setup-everything: echo "✅ MCP 'everything' server configured in $CONFIG_FILE" echo " Start the agent and run: /plugin enable mcp && /mcp enable everything" -# Set up the MCP GitHub server (requires GITHUB_TOKEN env var) +# Set up the MCP GitHub server (uses GITHUB_TOKEN — get one via: gh auth token) [unix] mcp-setup-github: #!/usr/bin/env bash @@ -614,9 +614,16 @@ mcp-setup-github: mkdir -p "$CONFIG_DIR" if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set. The GitHub MCP server needs it at runtime." - echo " export GITHUB_TOKEN=ghp_your_token_here" - echo " Continuing with config anyway..." + echo "⚠️ GITHUB_TOKEN not set. Trying 'gh auth token'..." + if command -v gh &>/dev/null; then + export GITHUB_TOKEN=$(gh auth token 2>/dev/null || true) + fi + if [ -z "${GITHUB_TOKEN:-}" ]; then + echo " Could not get token. Run: export GITHUB_TOKEN=\$(gh auth token)" + echo " Continuing with config anyway..." + else + echo " ✅ Got token from gh CLI" + fi fi node -e " @@ -638,7 +645,7 @@ mcp-setup-github: fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n'); " echo "✅ MCP 'github' server configured in $CONFIG_FILE" - echo " Requires: export GITHUB_TOKEN=ghp_..." + echo " Tip: export GITHUB_TOKEN=\$(gh auth token)" echo " Start the agent and run: /plugin enable mcp && /mcp enable github" # Set up the MCP filesystem server (read-only access to a directory) diff --git a/builtin-modules/pdf.json b/builtin-modules/pdf.json index 8e46e4a..4339632 100644 --- a/builtin-modules/pdf.json +++ b/builtin-modules/pdf.json @@ -3,7 +3,7 @@ "description": "PDF 1.7 document generation — text, graphics, metadata, standard fonts. Flow-based layout for auto-paginating documents.", "author": "system", "mutable": false, - "sourceHash": "sha256:c8716bcb3295f5bc", + "sourceHash": "sha256:e6e604e51302ea45", "dtsHash": "sha256:f30fba88bfe5f977", "importStyle": "named", "hints": { diff --git a/builtin-modules/pptx-tables.json b/builtin-modules/pptx-tables.json index b7ceb94..350fcb5 100644 --- a/builtin-modules/pptx-tables.json +++ b/builtin-modules/pptx-tables.json @@ -3,7 +3,7 @@ "description": "Styled tables for PPTX presentations - headers, borders, alternating rows", "author": "system", "mutable": false, - "sourceHash": "sha256:e03a2365c45ab0e6", + "sourceHash": "sha256:4fd269d16f32d3ec", "dtsHash": "sha256:130d021921083af6", "importStyle": "named", "hints": { diff --git a/builtin-modules/src/pdf.ts b/builtin-modules/src/pdf.ts index 44955f2..c8fde77 100644 --- a/builtin-modules/src/pdf.ts +++ b/builtin-modules/src/pdf.ts @@ -3498,6 +3498,15 @@ function renderTable( const MIN_CONTRAST = 3.0; const pageBg = style._pageBg || "FFFFFF"; + // If headerBg is too similar to pageBg, the header row won't stand out. + // Swap to theme accent1 so the header is visually distinct. + if (style.headerBg) { + const headerVsPage = contrastRatio(style.headerBg, pageBg); + if (headerVsPage < 1.5 && doc.theme.accent1) { + style.headerBg = doc.theme.accent1; + } + } + if (style.bodyFg) { const bodyRatio = contrastRatio(style.bodyFg, pageBg); if (bodyRatio < MIN_CONTRAST) { @@ -3641,16 +3650,12 @@ function renderTable( }); } - // Alternating row background FIRST - if (style.altRowBg && r % 2 === 1) { - doc.drawRect(x, curY, totalWidth, rowH, { fill: style.altRowBg }); - } - - // Auto-contrast body text against effective row background + // EVERY row gets an explicit fill — no transparent rows, no guessing const isAlt = !!(style.altRowBg && r % 2 === 1); - const rowBg = isAlt - ? style.altRowBg - : (style._pageBg || "FFFFFF"); + const rowBg = isAlt ? style.altRowBg : (style._pageBg || "FFFFFF"); + doc.drawRect(x, curY, totalWidth, rowH, { fill: rowBg }); + + // Text color computed against the ACTUAL fill we just drew const rowFg = autoTextColor(rowBg); // Cell text AFTER background diff --git a/builtin-modules/src/pptx-tables.ts b/builtin-modules/src/pptx-tables.ts index 7a1ab01..f83075a 100644 --- a/builtin-modules/src/pptx-tables.ts +++ b/builtin-modules/src/pptx-tables.ts @@ -276,7 +276,7 @@ export function table(opts: TableOptions): ShapeFragment { // ── Theme-aware defaults ──────────────────────────────────────────── // If opts.theme is passed, auto-compute colors for dark/light backgrounds. // Style overrides always take precedence over theme-computed values. - const theme = opts.theme || {}; + const theme = (opts.theme || {}) as Partial; const darkMode = theme.bg ? isDark(theme.bg) : false; // Dark mode defaults: light text on dark alt-rows @@ -285,7 +285,14 @@ export function table(opts: TableOptions): ShapeFragment { const defaultAltRowColor = darkMode ? "2D333B" : "F5F5F5"; const defaultBorderColor = darkMode ? "444C56" : "CCCCCC"; - const headerBg = style.headerBg || "2196F3"; + let headerBg = style.headerBg || "2196F3"; + + // If headerBg matches the slide bg, the header won't stand out — use accent + const slideBgForCheck = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF"); + if (theme.accent1 && contrastRatio(headerBg, slideBgForCheck) < 1.5) { + headerBg = theme.accent1; + } + const headerColor = style.headerColor || autoTextColor(headerBg); const headerFontSize = style.headerFontSize || 13; const styleFontSize = style.fontSize || 12; @@ -324,10 +331,10 @@ export function table(opts: TableOptions): ShapeFragment { // Build data rows // ALWAYS auto-contrast text against each row's effective background // to prevent unreadable text on dark themes or image backgrounds. - // If no theme.bg is provided, give non-alt rows an explicit fill - // matching the alt-row scheme so text is always readable. + // ALWAYS give every row an explicit fill — never rely on slide background + // inheritance, because we can't guarantee we know the actual slide bg. + // This ensures autoTextColor always computes against the real fill. const slideBg = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF"); - const nonAltFill = darkMode ? slideBg : undefined; const dataRows = rows .map((row, rowIdx) => { const isAlt = altRows && rowIdx % 2 === 1; @@ -337,7 +344,7 @@ export function table(opts: TableOptions): ShapeFragment { const cells = row .map((cell) => cellXml(cell, { - fillColor: isAlt ? altRowColor : nonAltFill, + fillColor: rowBg, // Always explicit fill — never undefined color: rowTextColor, fontSize: styleFontSize, borderColor, diff --git a/docs/MCP.md b/docs/MCP.md index 9284913..95c4993 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -205,8 +205,8 @@ MCP tools with native PPTX generation in a single workflow. ### Setup ```bash -# Set your GitHub token -export GITHUB_TOKEN="ghp_your_token_here" +# Use your existing GitHub CLI auth (no PAT needed) +export GITHUB_TOKEN=$(gh auth token) # Configure the GitHub MCP server just mcp-setup-github diff --git a/src/code-validator/guest/runtime/src/validator.rs b/src/code-validator/guest/runtime/src/validator.rs index ab8ee58..fc8c08f 100644 --- a/src/code-validator/guest/runtime/src/validator.rs +++ b/src/code-validator/guest/runtime/src/validator.rs @@ -304,13 +304,12 @@ fn sha256_short(content: &str) -> String { } /// Validate module.json hashes against actual source content. -/// System module mismatches are errors (potential tampering/corruption). -/// User module mismatches are warnings (they edit their own stuff). +/// Only checks system modules — user modules are mutable by design. fn validate_module_hashes( context: &ValidationContext, ) -> (Vec, Vec) { let mut errors = Vec::new(); - let mut warnings = Vec::new(); + let warnings = Vec::new(); for (specifier, json_str) in &context.module_jsons { // Parse the module.json @@ -319,7 +318,10 @@ fn validate_module_hashes( Err(_) => continue, // Skip malformed JSON }; - let is_system = meta.author == "system"; + // Skip user modules — they're mutable by design, hash drift is expected + if meta.author != "system" { + continue; + } // Check .js source hash if let Some(expected_hash) = &meta.source_hash @@ -327,26 +329,17 @@ fn validate_module_hashes( { let actual_hash = sha256_short(js_source); if expected_hash != &actual_hash { - let message = alloc::format!( - "{}: .js hash mismatch (expected {}, got {}). Run: npm run build:modules", - specifier, - expected_hash, - actual_hash - ); - if is_system { - errors.push(ValidationError { - error_type: "integrity".to_string(), - message, - line: None, - column: None, - }); - } else { - warnings.push(ValidationWarning { - warning_type: "drift".to_string(), - message, - line: None, - }); - } + errors.push(ValidationError { + error_type: "integrity".to_string(), + message: alloc::format!( + "{}: .js hash mismatch (expected {}, got {}). Run: npm run build:modules", + specifier, + expected_hash, + actual_hash + ), + line: None, + column: None, + }); } } @@ -356,26 +349,17 @@ fn validate_module_hashes( { let actual_hash = sha256_short(dts_source); if expected_hash != &actual_hash { - let message = alloc::format!( - "{}: .d.ts hash mismatch (expected {}, got {}). Run: npm run build:modules", - specifier, - expected_hash, - actual_hash - ); - if is_system { - errors.push(ValidationError { - error_type: "integrity".to_string(), - message, - line: None, - column: None, - }); - } else { - warnings.push(ValidationWarning { - warning_type: "drift".to_string(), - message, - line: None, - }); - } + errors.push(ValidationError { + error_type: "integrity".to_string(), + message: alloc::format!( + "{}: .d.ts hash mismatch (expected {}, got {}). Run: npm run build:modules", + specifier, + expected_hash, + actual_hash + ), + line: None, + column: None, + }); } } } diff --git a/tests/golden/pdf/signature-line.png b/tests/golden/pdf/signature-line.png index 70ca9fe..25a9451 100644 Binary files a/tests/golden/pdf/signature-line.png and b/tests/golden/pdf/signature-line.png differ diff --git a/tests/golden/pdf/table-styles.png b/tests/golden/pdf/table-styles.png index 5435dae..c85ae57 100644 Binary files a/tests/golden/pdf/table-styles.png and b/tests/golden/pdf/table-styles.png differ diff --git a/tests/golden/pdf/text-rendering.png b/tests/golden/pdf/text-rendering.png index b8d3d32..eae277b 100644 Binary files a/tests/golden/pdf/text-rendering.png and b/tests/golden/pdf/text-rendering.png differ diff --git a/tests/golden/pdf/title-page.png b/tests/golden/pdf/title-page.png index c90ed1b..0759296 100644 Binary files a/tests/golden/pdf/title-page.png and b/tests/golden/pdf/title-page.png differ diff --git a/tests/golden/pdf/two-column.png b/tests/golden/pdf/two-column.png index 10c0cb3..8d4bb18 100644 Binary files a/tests/golden/pdf/two-column.png and b/tests/golden/pdf/two-column.png differ