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
21 changes: 18 additions & 3 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Code Chat Browser</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css" id="hljs-theme">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<!-- SRI hashes pin each CDN asset to a specific known-good payload; the
browser refuses anything whose hash does not match (issue #19).
crossorigin="anonymous" is required for the browser to enforce SRI
on cross-origin requests. Hashes verified against cdnjs's SRI API:
curl https://api.cdnjs.com/libraries/<name>/<version>?fields=sri
NOTE: the runtime theme swap in static/js/app.js MUST also swap the
integrity attribute when it changes href — see HLJS_THEME_SHEETS. -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css"
integrity="sha512-mtXspRdOWHCYp+f4c7CkWGYPPRAhq9X+xCvJMUBVAb6pqA4U8pxhT3RWT3LP3bKbiolYL2CkL1bSKZZO4eeTew=="
crossorigin="anonymous"
id="hljs-theme">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"
integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ=="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"
integrity="sha512-pSeTnZAQF/RHxb0ysMoYQI/BRZsa5XuklcrgFfU3YZIdnD3LvkkqzrIeHxzFi6gKtI8Cpq2DEWdZjMTcNVhUYA=="
crossorigin="anonymous"></script>
</head>
<body>
<!-- Navbar -->
Expand Down
45 changes: 31 additions & 14 deletions static/js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
// Claude Code Chat Browser — Main JS

// Highlight.js theme stylesheets, keyed by theme name. Both `href` and
// `integrity` MUST be assigned together when swapping at runtime —
// changing `href` while leaving a stale `integrity` would make the
// browser refuse the new stylesheet and break the UI (issue #19).
// Hashes verified against cdnjs's SRI API. The corresponding static
// tag in static/index.html carries crossorigin="anonymous" which
// persists across runtime href swaps.
const HLJS_THEME_SHEETS = {
dark: {
href: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css',
integrity: 'sha512-mtXspRdOWHCYp+f4c7CkWGYPPRAhq9X+xCvJMUBVAb6pqA4U8pxhT3RWT3LP3bKbiolYL2CkL1bSKZZO4eeTew==',
},
light: {
href: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css',
integrity: 'sha512-0aPQyyeZrWj9sCA46UlmWgKOP0mUipLQ6OZXu8l4IcAmD2u31EPEy9VcIMvl7SoAaKe8bLXZhYoMaE/in+gcgA==',
},
};

function applyHljsTheme(themeName) {
const link = document.getElementById('hljs-theme');
if (!link) return;
const sheet = HLJS_THEME_SHEETS[themeName] || HLJS_THEME_SHEETS.dark;
// Set integrity FIRST, then href — the browser reads the current
// integrity at fetch time, and href change is what triggers the fetch.
link.integrity = sheet.integrity;
link.href = sheet.href;
}

function showToast(message, type = 'info') {
const icons = { success: '\u2713', error: '\u2717', info: '\u2139' };
const toast = document.createElement('div');
Expand Down Expand Up @@ -122,14 +150,8 @@ function setHamburgerVisible(visible) {
function setWorkspaceMode(active) {
// No container class change needed — workspace lives inside the standard container
document.body.classList.toggle('workspace-mode', active);
// Switch highlight.js theme
const hljsLink = document.getElementById('hljs-theme');
if (hljsLink) {
const theme = localStorage.getItem('theme') || 'dark';
hljsLink.href = theme === 'dark'
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css'
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css';
}
// Switch highlight.js theme — helper updates href + integrity together (issue #19).
applyHljsTheme(localStorage.getItem('theme') || 'dark');
}

let _navInProgress = false;
Expand Down Expand Up @@ -836,12 +858,7 @@ function applyTheme(theme) {
moon.style.display = theme === 'dark' ? 'block' : 'none';
sun.style.display = theme === 'light' ? 'block' : 'none';
}
const hljsLink = document.getElementById('hljs-theme');
if (hljsLink) {
hljsLink.href = theme === 'dark'
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css'
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css';
}
applyHljsTheme(theme); // href + integrity swapped together (issue #19)
}

function toggleTheme() {
Expand Down
Loading