Skip to content
Open
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
17 changes: 12 additions & 5 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 "
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion builtin-modules/pdf.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion builtin-modules/pptx-tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 14 additions & 9 deletions builtin-modules/src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment on lines +3503 to +3507
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderTable mutates the passed TableStyle (style.headerBg here). When callers use preset styles via resolveTableStyle, those are shared singletons from TABLE_STYLES, so this change can leak across tables/documents. Consider computing an effectiveHeaderBg local variable (and/or cloning the style object when resolving presets) instead of mutating style in-place.

Copilot uses AI. Check for mistakes.
}

if (style.bodyFg) {
const bodyRatio = contrastRatio(style.bodyFg, pageBg);
if (bodyRatio < MIN_CONTRAST) {
Expand Down Expand Up @@ -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 });
Comment on lines +3653 to +3656
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rowBg falls back to style._pageBg, but _pageBg is stored on the (potentially shared) TableStyle and only set once earlier. With the new “always draw a fill” behavior, a stale _pageBg from a previous render can cause incorrect non-alt row fills and contrast. Prefer using a local pageBg derived from doc.theme.bg for each call (don’t cache it on style).

Copilot uses AI. Check for mistakes.

// Text color computed against the ACTUAL fill we just drew
const rowFg = autoTextColor(rowBg);
Comment on lines +3658 to 3659
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Row text color is now always derived from autoTextColor(rowBg) (rowFg), but TableStyle.bodyFg is still validated/auto-corrected above and is never used anywhere in renderTable. Either apply the (auto-corrected) style.bodyFg when drawing row text, or remove/update the bodyFg logic/docs to avoid dead code and confusing API behavior.

Suggested change
// Text color computed against the ACTUAL fill we just drew
const rowFg = autoTextColor(rowBg);
// Prefer the validated body text color; fall back to an automatic
// contrast color if no explicit body foreground is configured.
const rowFg = style.bodyFg ?? autoTextColor(rowBg);

Copilot uses AI. Check for mistakes.

// Cell text AFTER background
Expand Down
19 changes: 13 additions & 6 deletions builtin-modules/src/pptx-tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Theme>;
const darkMode = theme.bg ? isDark(theme.bg) : false;

// Dark mode defaults: light text on dark alt-rows
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions docs/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 28 additions & 44 deletions src/code-validator/guest/runtime/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidationError>, Vec<ValidationWarning>) {
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
Expand All @@ -319,34 +318,28 @@ 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
&& let Some(js_source) = context.module_sources.get(specifier)
{
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,
});
}
}

Expand All @@ -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,
});
}
}
}
Expand Down
Binary file modified tests/golden/pdf/signature-line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/golden/pdf/table-styles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/golden/pdf/text-rendering.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/golden/pdf/title-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/golden/pdf/two-column.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading