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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Keep code readable by a literate technical user in a single sitting. Prefer the
- Source, build, and dist never overlap. Source directories hold only hand-authored code. `build/` (top-level, mirroring `packages/` and `pages/` — e.g. `build/packages/kdbx/`, `build/pages/0x67/`) holds intermediate, always-regenerated compiler and bundler output. `dist/` (top-level) holds the final distributables. Neither `build/` nor `dist/` is ever committed; deleting either never loses anything that can't be regenerated by `npm run build`.
- One version number, at the repo root. Packages under `packages/` don't carry independent versions — they aren't published.
- Biome lint and format are enforced with no exceptions. `npm run lint` must be clean before a change is done.
- Comments in code that actually ships — each package's `src/` (`packages/*/src/`), and each page's `page.ts`, `logic.ts`, and `page.css` — are compact, because every dist file ships this text verbatim (see [Reproducing a build][reproducing] / the build pipeline: nothing minifies or strips comments), so a wordy comment is wordy in every user's download, not just in the editor. Content that fits on one line is an end-of-line comment (e.g., `//`) where the language has one; content that genuinely needs two or more lines, or where the language has no end-of-line form at all (CSS only has `/* */`), is a block comment. This is decided on the *final*, trimmed wording, not the original — if trimming a block down to its essential point leaves only one line, it becomes an end-of-line comment wherever that's available, not a one-line block. A block comment is compact: the opening delimiter is immediately followed by content on the same line, continuation lines carry no `* ` gutter and are indented to match the surrounding code (not offset for an asterisk), and the closing delimiter sits at the end of the last content line, never alone on its own line. Multi-line comments are trimmed to their essential point — the non-obvious why, spec/RFC citations, security rationale — not restated context, illustrative examples, or a treatise. None of this applies to `tools/`, `e2e/`, `*.test.ts`, or any other test/tooling code: nothing there ships in a dist file, so ordinary, fuller commenting is fine. Biome does not enforce any of this — it's a manual review point, not a lint failure.

## Quality requirements

Expand Down
6 changes: 3 additions & 3 deletions e2e/cloud-google-drive-iframe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ test('embedded 0x67 iframe fills its container, and its own footer stays hidden'
const { frameHeight, availableHeight } = await page.evaluate(() => {
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
const header = document.querySelector('.host-header') as HTMLElement;
const sponsorCta = document.querySelector('.sponsor-cta') as HTMLElement;
const footerLinks = document.querySelector('.footer-links') as HTMLElement;
const footer = document.querySelector('footer') as HTMLElement;
const claimedByOthers =
header.getBoundingClientRect().height +
sponsorCta.getBoundingClientRect().height +
footerLinks.getBoundingClientRect().height +
footer.getBoundingClientRect().height;
return {
frameHeight: iframe.getBoundingClientRect().height,
Expand All @@ -69,7 +69,7 @@ test('embedded 0x67 iframe fills its container, and its own footer stays hidden'
assert.ok(
Math.abs(frameHeight - availableHeight) < 3,
`iframe height (${frameHeight}px) should fill the space left over after ` +
`the header/sponsor-cta/footer (${availableHeight}px), not collapse`,
`the header/footer-links/footer (${availableHeight}px), not collapse`,
);

const outerFooterVisible = await page.evaluate(() => {
Expand Down
3 changes: 2 additions & 1 deletion pages/0x67/build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"template": "page.html",
"styles": ["page.css"],
"styles": ["../shared/footer.css", "page.css"],
"scripts": ["../../build/pages/0x67/bundle.js"],
"footer": "../shared/footer.html",
"output": "../../dist/0x67.html"
}
38 changes: 1 addition & 37 deletions pages/0x67/page.css
Original file line number Diff line number Diff line change
Expand Up @@ -1376,44 +1376,8 @@ input.attachment-name:focus {
layout's justify-content:center)
============================================================ */

.sponsor-cta {
flex-shrink: 0;
padding: 0.4rem 1rem 0;
font-size: 0.8rem;
color: var(--text);
text-align: center;
}

footer {
flex-shrink: 0;
padding: 0.4rem 1rem;
border-top: 1px solid var(--border);
font-size: 0.75rem;
color: var(--muted);
text-align: center;
}

footer a {
color: var(--accent);
text-decoration: none;
}

.sponsor-cta a {
color: var(--accent);
font-weight: 600;
text-decoration: none;
}

footer a:hover {
text-decoration: underline;
}

.sponsor-cta a:hover {
text-decoration: underline;
}

/* Embedded: the host page has its own footer, so hide this copy. */
body.embedded .sponsor-cta,
body.embedded .footer-links,
body.embedded footer {
display: none;
}
Expand Down
5 changes: 1 addition & 4 deletions pages/0x67/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,7 @@ <h2>Choose an icon</h2>
</div>
</dialog>

<p class="sponsor-cta">
Like KeePass Web? <a href="https://github.com/sponsors/keepass-web" target="_blank" rel="noopener noreferrer">&hearts; Sponsor</a> its development.
</p>
<footer>MIT licensed &middot; <a href="https://github.com/keepass-web">github.com/keepass-web</a> &middot; <!--VERSION--></footer>
<!--FOOTER-->

<!--SCRIPTS-->
</body>
Expand Down
3 changes: 2 additions & 1 deletion pages/cloud-google-drive/build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"template": "page.html",
"styles": ["page.css"],
"styles": ["../shared/footer.css", "page.css"],
"scripts": ["../../build/pages/cloud-google-drive/bundle.js"],
"footer": "../shared/footer.html",
"output": "../../dist/cloud-google-drive.html"
}
26 changes: 0 additions & 26 deletions pages/cloud-google-drive/page.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,32 +98,6 @@ body {
min-height: 0;
}

.sponsor-cta {
flex-shrink: 0;
padding: 0.4rem 1rem 0;
font-size: 0.85rem;
color: var(--text);
text-align: center;
}

footer {
flex-shrink: 0;
padding: 0.4rem 1rem;
border-top: 1px solid var(--border);
font-size: 0.8rem;
color: var(--muted);
text-align: center;
}

footer a {
color: var(--accent);
}

.sponsor-cta a {
color: var(--accent);
font-weight: 600;
}

/* ============================================================
Buttons and shared bits
============================================================ */
Expand Down
5 changes: 1 addition & 4 deletions pages/cloud-google-drive/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ <h1>Open from Google Drive</h1>
</div>
</template>

<p class="sponsor-cta">
Like KeePass Web? <a href="https://github.com/sponsors/keepass-web" target="_blank" rel="noopener noreferrer">&hearts; Sponsor</a> its development.
</p>
<footer>MIT licensed &middot; <a href="https://github.com/keepass-web">github.com/keepass-web</a> &middot; <!--VERSION--></footer>
<!--FOOTER-->

<!--SCRIPTS-->
</body>
Expand Down
3 changes: 2 additions & 1 deletion pages/index/build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"template": "page.html",
"styles": ["page.css"],
"styles": ["../shared/footer.css", "page.css"],
"scripts": [],
"footer": "../shared/footer.html",
"output": "../../dist/index.html"
}
22 changes: 0 additions & 22 deletions pages/index/page.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,25 +139,3 @@ a:hover {
margin-bottom: 2rem;
text-align: left;
}

.links {
display: flex;
gap: 1.5rem;
justify-content: center;
}

.sponsor-cta {
margin-top: 2rem;
font-size: 0.9rem;
color: var(--text);
}

.sponsor-cta a {
font-weight: 600;
}

footer {
margin-top: 4rem;
font-size: 0.8rem;
color: var(--border);
}
10 changes: 1 addition & 9 deletions pages/index/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,8 @@ <h1>Your passwords.<br>Your storage.<br>Your browser.</h1>
Every file on the hosted site is a verbatim copy of a published release artifact.
Download the release, verify the published checksum, and trust what you audit.
</div>

<div class="links">
<a href="https://github.com/keepass-web">GitHub</a>
</div>

<p class="sponsor-cta">
Like KeePass Web? <a href="https://github.com/sponsors/keepass-web" target="_blank" rel="noopener noreferrer">&hearts; Sponsor</a> its development.
</p>
</main>
<footer>MIT licensed &middot; <a href="https://github.com/keepass-web">github.com/keepass-web</a> &middot; <!--VERSION--></footer>
<!--FOOTER-->
<!--SCRIPTS-->
</body>
</html>
3 changes: 2 additions & 1 deletion pages/local/build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"template": "page.html",
"styles": ["page.css"],
"styles": ["../shared/footer.css", "page.css"],
"scripts": ["../../build/pages/local/bundle.js"],
"footer": "../shared/footer.html",
"output": "../../dist/local.html"
}
26 changes: 0 additions & 26 deletions pages/local/page.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,6 @@ a:hover {
text-decoration: underline;
}

.sponsor-cta {
flex-shrink: 0;
padding: 0.4rem 1rem 0;
font-size: 0.85rem;
color: var(--text);
text-align: center;
}

footer {
flex-shrink: 0;
padding: 0.4rem 1rem;
border-top: 1px solid var(--border);
font-size: 0.8rem;
color: var(--muted);
text-align: center;
}

footer a {
color: var(--accent);
}

.sponsor-cta a {
color: var(--accent);
font-weight: 600;
}

.description {
color: var(--muted);
}
Expand Down
5 changes: 1 addition & 4 deletions pages/local/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ <h1>Open a database from this computer</h1>
</div>
</template>

<p class="sponsor-cta">
Like KeePass Web? <a href="https://github.com/sponsors/keepass-web" target="_blank" rel="noopener noreferrer">&hearts; Sponsor</a> its development.
</p>
<footer>MIT licensed &middot; <a href="https://github.com/keepass-web">github.com/keepass-web</a> &middot; <!--VERSION--></footer>
<!--FOOTER-->

<!--SCRIPTS-->
</body>
Expand Down
39 changes: 39 additions & 0 deletions pages/shared/footer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* ============================================================
Shared footer partial (pages/shared/footer.html)
============================================================ */

footer {
flex-shrink: 0;
padding: 0.4rem 1rem;
border-top: 1px solid var(--border);
font-size: 0.8rem;
color: var(--muted);
text-align: center;
}

footer a {
color: var(--accent);
text-decoration: none;
}

footer a:hover {
text-decoration: underline;
}

.footer-links {
flex-shrink: 0;
padding: 0.4rem 1rem 0;
font-size: 0.85rem;
color: var(--text);
text-align: center;
}

.footer-links a {
color: var(--accent);
font-weight: 600;
text-decoration: none;
}

.footer-links a:hover {
text-decoration: underline;
}
7 changes: 7 additions & 0 deletions pages/shared/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p class="footer-links">
<a href="https://github.com/sponsors/keepass-web" target="_blank" rel="noopener noreferrer">&hearts; Sponsor</a>
&middot; <a href="https://github.com/keepass-web/source-application/blob/main/LICENSE">MIT Licensed</a>
&middot; <a href="https://github.com/keepass-web/.github/blob/main/profile/FAQ.md">FAQ</a>
&middot; <a href="https://github.com/keepass-web/source-application">Source</a>
</p>
<footer><!--VERSION--></footer>
33 changes: 23 additions & 10 deletions tools/build/inliner/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import { computeVersionLabel, renderVersionFragment } from './version-label.ts';

const STYLES_SENTINEL = '<!--STYLES-->';
const SCRIPTS_SENTINEL = '<!--SCRIPTS-->';
const FOOTER_SENTINEL = '<!--FOOTER-->';
const VERSION_SENTINEL = '<!--VERSION-->';

/**
* Reads the manifest at `manifestPath`, inlines all styles and scripts into the
* HTML template in the order listed, writes the output file, and returns its
* SHA-256 checksum (hex-encoded) alongside the resolved output path.
* Reads the manifest at `manifestPath`, inlines all styles and scripts plus the
* shared footer partial into the HTML template in the order listed, writes the
* output file, and returns its SHA-256 checksum (hex-encoded) alongside the
* resolved output path.
*
* Throws if either sentinel is absent from the template, or if any listed file
* Throws if any sentinel is absent from the template, or if any listed file
* cannot be read.
*/
export function build(manifestPath: string): { checksum: string; outputPath: string } {
Expand All @@ -28,12 +30,26 @@ export function build(manifestPath: string): { checksum: string; outputPath: str
if (!template.includes(SCRIPTS_SENTINEL)) {
throw new Error(`Template is missing the required sentinel: ${SCRIPTS_SENTINEL}`);
}
if (!template.includes(VERSION_SENTINEL)) {
throw new Error(`Template is missing the required sentinel: ${VERSION_SENTINEL}`);
if (!template.includes(FOOTER_SENTINEL)) {
throw new Error(`Template is missing the required sentinel: ${FOOTER_SENTINEL}`);
}

const css = manifest.styles.map((f) => readFileSync(join(base, f), 'utf8')).join('\n');
const js = manifest.scripts.map((f) => readFileSync(join(base, f), 'utf8')).join('\n');
const footer = readFileSync(join(base, manifest.footer), 'utf8');

const withFooter = template
.replace(STYLES_SENTINEL, `<style>\n${css}\n</style>`)
.replace(SCRIPTS_SENTINEL, `<script>\n${js}\n</script>`)
.replace(FOOTER_SENTINEL, footer);

// VERSION may live directly in the template or inside the just-inlined
// footer partial (the real page templates only carry it via the footer),
// so this check runs after FOOTER inlining rather than alongside the rest.
if (!withFooter.includes(VERSION_SENTINEL)) {
throw new Error(`Template is missing the required sentinel: ${VERSION_SENTINEL}`);
}

const version = renderVersionFragment(
computeVersionLabel({
refType: process.env.GITHUB_REF_TYPE,
Expand All @@ -43,10 +59,7 @@ export function build(manifestPath: string): { checksum: string; outputPath: str
process.env.KEEPASS_WEB_COMMIT_DATE,
);

const html = template
.replace(STYLES_SENTINEL, `<style>\n${css}\n</style>`)
.replace(SCRIPTS_SENTINEL, `<script>\n${js}\n</script>`)
.replace(VERSION_SENTINEL, version);
const html = withFooter.replace(VERSION_SENTINEL, version);

const outputPath = join(base, manifest.output);
mkdirSync(dirname(outputPath), { recursive: true });
Expand Down
10 changes: 9 additions & 1 deletion tools/build/inliner/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
* load order — it is the author's responsibility to list dependencies first.
*/
export interface Manifest {
/** HTML template. Must contain exactly one <!--STYLES-->, one <!--SCRIPTS-->, and one <!--VERSION--> sentinel. */
/**
* HTML template. Must contain exactly one <!--STYLES-->, one <!--SCRIPTS-->,
* and one <!--FOOTER--> sentinel. A <!--VERSION--> sentinel must appear
* exactly once across the template and/or the inlined footer partial —
* it's resolved after FOOTER inlining, so the footer partial may supply it
* instead of the template.
*/
readonly template: string;
/** CSS files, concatenated in order and inlined at <!--STYLES-->. */
readonly styles: readonly string[];
/** JS files, concatenated in order and inlined at <!--SCRIPTS-->. */
readonly scripts: readonly string[];
/** HTML partial inlined at <!--FOOTER-->, before <!--VERSION--> is resolved. */
readonly footer: string;
/** Output path for the produced distributable, relative to the manifest. */
readonly output: string;
}
Loading