mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
60661a0d7e
SignPath Foundation's OSS program requires the attribution notice, and a route to the code signing policy, on a project's home page and download/release pages. Worktrunk had both only on the policy page itself — nothing on the README or the docs landing page. This puts the notice in the install section's Windows block, beside the artifacts it actually describes: > Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) — [policy](https://worktrunk.dev/code-signing/). The edit is one line in `docs/content/worktrunk.md`; the README's Install→Further reading block is generated from it, so it propagates there and to both skill mirrors. The policy page leaves the docs navigation in the same change, so this is the single place it's linked from. `hide_from_nav = true` in a page's `[extra]` skips it in all three loops over `docs_section.pages`: the desktop TOC (`macros.html`), the mobile menu (`base.html`), and the prev/next flow nav (`page.html`). The page stays published and reachable at `/code-signing/` — unlisted, not removed. In the nav loops the skip wraps the whole per-page body, so a hidden page can't emit a stray group heading. In the prev/next loop it guards only the *candidate* assignments — the current-page test stays unguarded, because viewing a hidden page directly must still flip `found_current` or its own neighbours compute against the wrong page. Verified both directions: FAQ ends at `← Tips & Patterns` with no forward link, and the policy page keeps `← FAQ` back out into the docs. <details><summary>Why this came up, and the placement tradeoff</summary> Found while debugging why the `release-signing` signing policy shows INVALID in the SignPath console. That turned out to be unrelated and not fixable here — its certificate ("Release certificate 2026", subject `CN=SignPath Foundation`, on SignPath's HSM) is in `CSR PENDING` with no validity dates, awaiting CA issuance. The policy's own configuration is complete and correct. Worktrunk currently signs with the test certificate, which is VALID. The attribution gap was the one thing found on our side. Whether it bears on the pending review is unknown — the console exposes no application status. On placement: the notice sits inside the collapsed `<details>`, so it isn't visible until a reader expands "Windows & other". That's deliberate — the signing is Windows-specific and the notice reads better next to it than in the page chrome — but it is the least prominent placement that still counts as a link, and the terms ask for the notice *on* the home and download pages. Worth knowing if placement is ever queried during review. </details> > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
292 lines
16 KiB
HTML
292 lines
16 KiB
HTML
{% import "macros.html" as macros %}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<script>
|
|
// 1Password extension bug workaround: MUST be first script in document.
|
|
// 1Password injects Prism.js targeting [class*="language-"], stripping our
|
|
// syntect spans. Fix: rename language-* to lang-* before 1Password sees them.
|
|
// https://1password.community/discussion/165597
|
|
(function() {
|
|
function fix(el) {
|
|
if (el.className && el.className.replace) {
|
|
el.className = el.className.replace(/\blanguage-\S*/g, '');
|
|
}
|
|
if (el.dataset && el.dataset.lang) el.removeAttribute('data-lang');
|
|
}
|
|
new MutationObserver(function(mutations) {
|
|
for (var i = 0; i < mutations.length; i++) {
|
|
var nodes = mutations[i].addedNodes;
|
|
for (var j = 0; j < nodes.length; j++) {
|
|
var n = nodes[j];
|
|
if (n.nodeType === 1) {
|
|
if (n.className && n.className.includes && n.className.includes('language-')) fix(n);
|
|
var els = n.querySelectorAll ? n.querySelectorAll('[class*="language-"]') : [];
|
|
for (var k = 0; k < els.length; k++) fix(els[k]);
|
|
}
|
|
}
|
|
}
|
|
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
})();
|
|
</script>
|
|
<meta charset="UTF-8">
|
|
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
|
|
{% include "_variables.html" %}
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Plus+Jakarta+Sans:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<link rel="preload" href="{{ get_url(path='logo@2x.png') }}" as="image" media="(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)">
|
|
<link rel="preload" href="{{ get_url(path='logo.png') }}" as="image" media="not all and (-webkit-min-device-pixel-ratio: 2), not all and (min-resolution: 192dpi)">
|
|
|
|
<script>
|
|
// iOS viewport jank fix: Firefox/Chrome iOS have broken svh/lvh units that
|
|
// behave like dvh, causing the hero to resize when browser chrome appears.
|
|
// Root cause: Missing WebKit minimumViewportInset/maximumViewportInset APIs.
|
|
// https://github.com/mozilla-mobile/firefox-ios/issues/11574
|
|
//
|
|
// Solution: Capture height ONCE on load, set --vh-full CSS variable.
|
|
// CSS uses var(--vh-full, 100lvh) so iOS gets stable pixels, others get lvh.
|
|
(function() {
|
|
var isIOS = /iP(hone|ad|od)/.test(navigator.platform) ||
|
|
(/Macintosh/.test(navigator.userAgent) && 'ontouchend' in document);
|
|
if (!isIOS) return;
|
|
|
|
var h = window.innerHeight;
|
|
if (h) {
|
|
document.documentElement.style.setProperty('--vh-full', h + 'px');
|
|
}
|
|
|
|
window.addEventListener('orientationchange', function() {
|
|
setTimeout(function() {
|
|
var newH = window.innerHeight;
|
|
if (newH) {
|
|
document.documentElement.style.setProperty('--vh-full', newH + 'px');
|
|
}
|
|
}, 100);
|
|
});
|
|
})();
|
|
</script>
|
|
<meta name="view-transition" content="same-origin">
|
|
{% set page_description = page.description | default(value=config.extra.site_description) %}
|
|
<meta name="description" content="{{ page_description }}">
|
|
{% if page.extra.canonical %}
|
|
<link rel="canonical" href="{{ config.base_url | trim_end_matches(pat='/') }}{{ page.extra.canonical }}">
|
|
{% else %}
|
|
<link rel="canonical" href="{{ current_url | default(value=config.base_url) }}">
|
|
{% endif %}
|
|
|
|
<!-- OpenGraph -->
|
|
{% set og_title = page.title | default(value="") %}
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="{% if page.extra.canonical %}{{ config.base_url | trim_end_matches(pat='/') }}{{ page.extra.canonical }}{% else %}{{ current_url | default(value=config.base_url) }}{% endif %}">
|
|
{% if og_title %}{% set og_title_full = og_title ~ " | " ~ config.title %}{% else %}{% set og_title_full = config.title ~ " — " ~ config.extra.site_description %}{% endif %}
|
|
<meta property="og:title" content="{{ og_title_full }}">
|
|
<meta property="og:description" content="{{ page_description }}">
|
|
<meta property="og:image" content="{{ config.base_url }}assets/social/social-card.png">
|
|
<meta property="og:image:width" content="1200">
|
|
<meta property="og:image:height" content="630">
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="{{ og_title_full }}">
|
|
<meta name="twitter:description" content="{{ page_description }}">
|
|
<meta name="twitter:image" content="{{ config.base_url }}assets/social/social-card.png">
|
|
|
|
<!-- Structured data -->
|
|
{% if not page.title %}
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "WebSite",
|
|
"name": "Worktrunk",
|
|
"url": "https://worktrunk.dev/",
|
|
"description": "{{ config.extra.site_description }}"
|
|
}
|
|
</script>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "SoftwareApplication",
|
|
"name": "Worktrunk",
|
|
"url": "https://worktrunk.dev/",
|
|
"description": "{{ config.extra.site_description }}",
|
|
"applicationCategory": "DeveloperApplication",
|
|
"operatingSystem": "macOS, Linux, Windows",
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "0",
|
|
"priceCurrency": "USD"
|
|
},
|
|
"codeRepository": "https://github.com/max-sixty/worktrunk",
|
|
"license": "https://opensource.org/licenses/Apache-2.0"
|
|
}
|
|
</script>
|
|
{% endif %}
|
|
|
|
<!-- Google tag (gtag.js) -->
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-6YY8BDJ4QH"></script>
|
|
<script>
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
gtag('config', 'G-6YY8BDJ4QH');
|
|
</script>
|
|
|
|
<link rel="stylesheet" href="/normalize.css">
|
|
<link rel="preload" href="/custom.css" as="style">
|
|
<link rel="stylesheet" href="/custom.css">
|
|
<!-- Syntax highlighting themes - preload and load conditionally by color scheme -->
|
|
<link rel="preload" href="/giallo-light.css" as="style" media="(prefers-color-scheme: light)">
|
|
<link rel="preload" href="/giallo-dark.css" as="style" media="(prefers-color-scheme: dark)">
|
|
<link rel="stylesheet" href="/giallo-light.css" media="(prefers-color-scheme: light)">
|
|
<link rel="stylesheet" href="/giallo-dark.css" media="(prefers-color-scheme: dark)">
|
|
<script src="/elasticlunr.min.js" defer></script>
|
|
<script src="/search_index.en.js" defer></script>
|
|
<script src="/search.js" defer></script>
|
|
<script src="/code-copy.js" defer></script>
|
|
<script src="/mobile-menu.js" defer></script>
|
|
<script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>
|
|
<script>
|
|
// Show demo images with fade-in after they load (hides broken-image icon during loading)
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('figure.demo img').forEach(function(img) {
|
|
if (img.complete) {
|
|
img.classList.add('loaded');
|
|
} else {
|
|
img.addEventListener('load', function() { img.classList.add('loaded'); });
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- iOS 26 Liquid Glass fix: solid scrim behind translucent browser chrome -->
|
|
<div class="ios-chrome-scrim" aria-hidden="true"></div>
|
|
<header>
|
|
<button class="mobile-menu-toggle" aria-label="Open menu" aria-expanded="false">
|
|
<svg class="menu-icon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
</svg>
|
|
<svg class="close-icon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
<a href="/">
|
|
<div class="logo">
|
|
<img src="{{ get_url(path=config.extra.logo_path) }}" srcset="{{ get_url(path='logo.png') }} 1x, {{ get_url(path='logo@2x.png') }} 2x" alt="logo" width="44" height="44" decoding="sync" fetchpriority="high">
|
|
{{ config.extra.logo_name }}
|
|
</div>
|
|
</a>
|
|
<nav>
|
|
<div class="search-container">
|
|
<input id="userinput" type="search" placeholder="Search..." aria-label="Search documentation">
|
|
<div id="suggestions"></div>
|
|
</div>
|
|
{% for menu in config.extra.extra_menu %}
|
|
<a class="nav-item" href="{{ menu.link | safe }}">{{ menu.title }}</a>
|
|
{% endfor %}
|
|
</nav>
|
|
</header>
|
|
|
|
<!-- Mobile menu drawer -->
|
|
<div class="mobile-menu-overlay" aria-hidden="true"></div>
|
|
<nav class="mobile-menu" aria-hidden="true">
|
|
<div class="mobile-menu-content">
|
|
<div class="mobile-menu-section">
|
|
{% for menu in config.extra.extra_menu %}
|
|
<a class="mobile-nav-item" href="{{ menu.link | safe }}">{{ menu.title }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="mobile-menu-divider"></div>
|
|
<div class="mobile-menu-section mobile-menu-toc">
|
|
{% set docs_section = get_section(path="_index.md") %}
|
|
{% set current_group = "" %}
|
|
{% for doc_page in docs_section.pages %}
|
|
{% if not doc_page.extra.hide_from_nav %}
|
|
{% set page_group = doc_page.extra.group | default(value="") %}
|
|
{% if page_group != current_group and page_group != "" %}
|
|
<div class="mobile-toc-group">{{ page_group }}</div>
|
|
{% set_global current_group = page_group %}
|
|
{% endif %}
|
|
<a class="mobile-toc-item" href="{{ doc_page.permalink | safe }}">{{ doc_page.title }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="hero">
|
|
{% block hero %}{% endblock hero %}
|
|
</div>
|
|
|
|
<main>
|
|
{% block toc %}
|
|
{% set docs_section = get_section(path="_index.md") %}
|
|
{{ macros::toc_nav(docs_section=docs_section) }}
|
|
{% endblock toc %}
|
|
|
|
<div class="content" id="{% block content_id %}{% endblock content_id %}">
|
|
{% block content %}
|
|
{{ section.content | safe }}
|
|
{% endblock content %}
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="footer-social">
|
|
<a href="https://github.com/max-sixty/worktrunk" aria-label="GitHub" title="GitHub">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
|
</svg>
|
|
</a>
|
|
<a href="https://crates.io/crates/worktrunk" aria-label="Crates.io" title="Crates.io">
|
|
<svg width="20" height="20" viewBox="0 0 512 512" fill="currentColor">
|
|
<path d="M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262.4 2.2 249.6 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v160.4l-160 53.5z"/>
|
|
</svg>
|
|
</a>
|
|
<a href="https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev" aria-label="Share on X" title="Share on X">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
|
</svg>
|
|
</a>
|
|
<a href="https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management" aria-label="Share on Reddit" title="Share on Reddit">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/>
|
|
</svg>
|
|
</a>
|
|
<a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev" aria-label="Share on LinkedIn" title="Share on LinkedIn">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</footer>
|
|
<script>
|
|
// Wrap code blocks in containers SYNCHRONOUSLY before first paint to prevent layout jump.
|
|
// The deferred code-copy.js will add copy buttons to these wrappers later.
|
|
(function() {
|
|
var blocks = document.querySelectorAll('.content pre');
|
|
for (var i = 0; i < blocks.length; i++) {
|
|
var block = blocks[i];
|
|
var wrapper = document.createElement('div');
|
|
wrapper.className = 'code-block-wrapper';
|
|
block.parentNode.insertBefore(wrapper, block);
|
|
wrapper.appendChild(block);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|