Skip to content
Closed
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
8 changes: 5 additions & 3 deletions app/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ const PAGE = manPage({
title: "404 — justhtml.sh",
bodyHtml: `
<h2>NOT FOUND</h2>
<div class="body"><pre>404 — there is nothing at this path.</pre></div>
<div class="body"><p>404 — there is nothing at this path.</p></div>

<h2>SEE ALSO</h2>
<div class="body"><pre><a href="/">/</a> what justhtml.sh is
<a href="/api/health">/api/health</a> service + database health</pre></div>
<div class="body">
<p><a href="/">/</a> — what justhtml.sh is</p>
<p><a href="/api/health">/api/health</a> — service + database health</p>
</div>
`,
});

Expand Down
13 changes: 7 additions & 6 deletions app/bookmarks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export const dynamic = "force-dynamic";

const LIST_LIMIT = 500;

// Rows lay out as a flex line: the man-page `<pre>` (title + dimmed tail) and a
// Rows lay out as a flex line: the row's main text (title + dimmed tail) and a
// quiet inline `remove` form sitting on the same baseline.
const ROW_STYLE = `
<style>
.row { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.1rem 0; }
.row pre { min-width: 0; }
.row .main { min-width: 0; }
.row a.title { font-weight: 700; }
.row .tail { color: #888; }
.row.revoked .title { color: #999; }
Expand Down Expand Up @@ -69,7 +69,7 @@ async function bookmarkAccess(

function emptySection(heading: string, copy: string): string {
return `<h2>${heading}</h2>
<div class="body"><pre>${copy}</pre></div>`;
<div class="body"><p>${copy}</p></div>`;
}

function section(heading: string, rows: string[]): string {
Expand All @@ -94,7 +94,8 @@ export async function GET(req: Request): Promise<Response> {
else shared.push(bookmark);
}

const intro = `<div class="body"><pre>Signed in as <code>${esc(email)}</code>.\n\nBookmarks track current access. If a doc's access is later revoked it stays\nlisted with a revoked label and no link — use remove to drop it.</pre></div>`;
const intro = `<div class="body"><p>Signed in as <code>${esc(email)}</code>.</p>
<p>Bookmarks track current access. If a doc's access is later revoked it stays listed with a revoked label and no link — use remove to drop it.</p></div>`;

// Revoked rows show the title snapshotted at bookmark time (bookmark_title),
// never the doc's current title, which the viewer can no longer see.
Expand Down Expand Up @@ -136,13 +137,13 @@ export async function GET(req: Request): Promise<Response> {
? section("YOUR DOCUMENTS", ownedRows)
: emptySection(
"YOUR DOCUMENTS",
"You haven't bookmarked any of your own documents yet. Use the bookmark\nbutton on a doc to save it here."
"You haven't bookmarked any of your own documents yet. Use the bookmark button on a doc to save it here."
),
shared.length
? section("SHARED WITH YOU", sharedRows)
: emptySection(
"SHARED WITH YOU",
"Nothing shared with you is bookmarked yet. Use the bookmark button on a\ndoc to save it here."
"Nothing shared with you is bookmarked yet. Use the bookmark button on a doc to save it here."
),
].join("\n");

Expand Down
26 changes: 15 additions & 11 deletions app/d/[slug]/CommentsShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import { startVersionPolling } from "@/lib/docs/version-polling";
// vertical selection toolbar (add comment + react). All persistence goes through
// /api/v1/docs/:slug/comments and /reactions; the API enforces permissions.

// Chrome (top bar, rail, composer, buttons) uses the site-wide system sans
// reading face; code and the inline-code chips stay monospace. Comment bodies
// keep their Georgia serif (.jh-md below).
const SANS = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`;
const MONO = `ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace`;
// The picker set — the curated brand emoji (birthday.md B11 example set). Every
// one is in the server's ALLOWED_EMOJI (lib/docs/reactions.ts); agents may also
Expand Down Expand Up @@ -1253,7 +1257,7 @@ function paletteVars(p: ChromePalette): React.CSSProperties {

// --------------------------- subcomponents ---------------------------

// Light / dark / auto theme control in the chrome bar. Segmented, monospace,
// Light / dark / auto theme control in the chrome bar. Segmented,
// themed off the same --jh-tog-* vars as the comment toggle so it recolors in
// dark. "auto" (◐) matches the document; ☀/☾ force the choice and persist it.
const THEME_OPTS: { m: ThemeMode; glyph: string; label: string }[] = [
Expand Down Expand Up @@ -1739,7 +1743,7 @@ function LinkGlyph() {
// attribute. Desktop (>768px): collapsing hides the rail to reclaim width; the
// scrim + ✕ are display:none. Mobile (<=768px): the doc is full-width and the
// rail is an off-canvas right drawer that slides in over the doc with a scrim.
// Light mode only, monospace chrome — consistent with the rest of the shell.
// Light mode only, sans chrome — consistent with the rest of the shell.
const RAIL_CSS = `
/* Read-time chip. Kernel brand palette: the fill gets heavier as the estimate grows —
kernel-green #81b300 under 5 min, beige-muted #e1dccf to 15, charcoal #212225 past
Expand Down Expand Up @@ -1899,8 +1903,8 @@ const barStyle: React.CSSProperties = {
alignItems: "center",
height: "2.4rem",
padding: "0 1.25rem",
fontFamily: MONO,
fontSize: 13,
fontFamily: SANS,
fontSize: 14,
borderBottom: "1px solid var(--jh-bar-border, #ccc)",
color: "var(--jh-bar-fg, #111)",
background: "var(--jh-bar-bg, #fff)",
Expand All @@ -1921,7 +1925,7 @@ const railStyle: React.CSSProperties = {
background: "var(--jh-rail-bg, #fbfbfb)",
position: "relative",
overflowY: "auto",
fontFamily: MONO,
fontFamily: SANS,
transition: CHROME_TRANSITION,
};

Expand Down Expand Up @@ -1993,8 +1997,8 @@ const composerTextarea: React.CSSProperties = {
width: "100%",
border: "1px solid var(--jh-input-border, #d4d4d4)",
borderRadius: 4,
fontFamily: MONO,
fontSize: 12,
fontFamily: SANS,
fontSize: 13,
padding: 5,
resize: "vertical",
minHeight: 34,
Expand All @@ -2004,8 +2008,8 @@ const composerTextarea: React.CSSProperties = {

function composerBtn(disabled: boolean): React.CSSProperties {
return {
fontFamily: MONO,
fontSize: 12,
fontFamily: SANS,
fontSize: 13,
border: "1px solid var(--jh-btn-border, #111)",
background: "var(--jh-btn-bg, #111)",
color: "var(--jh-btn-fg, #fff)",
Expand Down Expand Up @@ -2044,8 +2048,8 @@ const toastStyle: React.CSSProperties = {
transform: "translateX(-50%)",
background: "var(--jh-sel-bg, #111)",
color: "var(--jh-sel-fg, #fff)",
fontFamily: MONO,
fontSize: 12,
fontFamily: SANS,
fontSize: 13,
padding: "7px 14px",
borderRadius: 7,
boxShadow: "0 4px 14px var(--jh-sel-shadow, rgba(0,0,0,.3))",
Expand Down
17 changes: 11 additions & 6 deletions app/d/[slug]/history/HistoryClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type Props = {
versions: VersionMeta[]; // newest first
};

// Chrome (headers, version list, buttons) uses the site-wide system sans reading
// face; the diff pane is code and stays monospace.
const SANS = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`;
const MONO = `ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace`;

function fmtBytes(n: number): string {
Expand Down Expand Up @@ -56,7 +59,7 @@ export default function HistoryClient({ slug, title, currentVersion, versions }:
const sel = versions.find((v) => v.version === selected);

return (
<div style={{ fontFamily: MONO, fontSize: 13, lineHeight: 1.55 }}>
<div style={{ fontFamily: SANS, fontSize: 14, lineHeight: 1.6 }}>
<div
style={{
display: "flex",
Expand Down Expand Up @@ -142,11 +145,13 @@ export default function HistoryClient({ slug, title, currentVersion, versions }:
</div>

{sel && sel.patch !== undefined ? (
<PatchDiff
key={`${sel.version}-${split ? "split" : "unified"}`}
patch={sel.patch}
options={{ diffStyle: split ? "split" : "unified", themeType: "light" }}
/>
<div style={{ fontFamily: MONO, fontSize: 13, lineHeight: 1.55 }}>
<PatchDiff
key={`${sel.version}-${split ? "split" : "unified"}`}
patch={sel.patch}
options={{ diffStyle: split ? "split" : "unified", themeType: "light" }}
/>
</div>
) : (
<p style={{ opacity: 0.6 }}>
No diff to show. This is the oldest retained snapshot.
Expand Down
43 changes: 23 additions & 20 deletions app/d/[slug]/history/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,57 @@
// the /d/:slug private notice; no existence oracle. Carries a real 404 status.
//
// Chrome: the LOCKED variant-A man-page chrome (birthday.md "Site-wide redesign
// decisions"). The root layout supplies <html>/<body> + the monospace brand;
// decisions"). The root layout supplies <html>/<body> + the sans reading face;
// this matches manPage()'s markup (headline + <h2> + .body + footer).

const BODY: React.CSSProperties = {
margin: "0 auto",
padding: "2rem 1.5rem 3rem",
maxWidth: 760,
fontFamily: `ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace`,
fontSize: 14,
lineHeight: 1.55,
fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`,
fontSize: 16,
lineHeight: 1.6,
color: "#111",
background: "#fff",
};

function footerLine(date: string): string {
const left = "justhtml.sh";
const right = "JUSTHTML.SH(1)";
const width = 78;
const gaps = Math.max(2, width - left.length - right.length - date.length);
const l = Math.ceil(gaps / 2);
const r = gaps - l;
return `${left}${" ".repeat(l)}${date}${" ".repeat(r)}${right}`;
// One quiet justified footer line, flex space-between so it stays flush at any
// wrap width. Mirrors manPage()'s footer.
function Footer({ date }: { date: string }) {
return (
<div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
<span>justhtml.sh</span>
<span>{date}</span>
<span>JUSTHTML.SH(1)</span>
</div>
);
}

export default function HistoryNotFound() {
const date = new Date().toISOString().slice(0, 10);
return (
<main style={BODY}>
<pre style={{ fontWeight: 700, margin: "0 0 1.5rem" }}>JUSTHTML.SH(1)</pre>
<div style={{ fontWeight: 700, margin: "0 0 1.5rem" }}>JUSTHTML.SH(1)</div>
<h2
style={{
fontSize: 14,
fontWeight: 700,
margin: "1.6rem 0 0.3rem",
margin: "1.6rem 0 0.4rem",
textTransform: "uppercase",
letterSpacing: "0.03em",
letterSpacing: "0.05em",
}}
>
PRIVATE OR NOT FOUND
</h2>
<div style={{ paddingLeft: "3.5ch" }}>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{`This document is private, or does not exist.

If you were given a link with a ?viewtoken=… on it, use that exact
link. The owner can rotate the token, which invalidates old links.`}</pre>
<p style={{ margin: "0 0 0.4rem" }}>This document is private, or does not exist.</p>
<p style={{ margin: 0 }}>
If you were given a link with a ?viewtoken=… on it, use that exact link.
The owner can rotate the token, which invalidates old links.
</p>
</div>
<footer style={{ marginTop: "2.5rem", color: "#666" }}>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{footerLine(date)}</pre>
<Footer date={date} />
</footer>
</main>
);
Expand Down
2 changes: 1 addition & 1 deletion app/d/[slug]/history/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default async function HistoryPage({ params, searchParams }: Props) {
margin: "0 auto",
maxWidth: 1100,
padding: "1.5rem 1.25rem 4rem",
fontFamily: `ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace`,
fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`,
}}
>
<HistoryClient
Expand Down
55 changes: 28 additions & 27 deletions app/d/[slug]/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,30 @@ import { headers } from "next/headers";
//
// Chrome: the LOCKED variant-A man-page chrome (birthday.md "Site-wide redesign
// decisions" — the private-doc notice, previously chrome-less, now reads as a
// full page). The root layout supplies <html>/<body> + the monospace brand;
// full page). The root layout supplies <html>/<body> + the sans reading face;
// this matches manPage()'s markup (headline + <h2> + .body + footer).

const BODY: React.CSSProperties = {
margin: "0 auto",
padding: "2rem 1.5rem 3rem",
maxWidth: 760,
fontFamily: `ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace`,
fontSize: 14,
lineHeight: 1.55,
fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`,
fontSize: 16,
lineHeight: 1.6,
color: "#111",
background: "#fff",
};

function footerLine(date: string): string {
const left = "justhtml.sh";
const right = "JUSTHTML.SH(1)";
const width = 78;
const gaps = Math.max(2, width - left.length - right.length - date.length);
const l = Math.ceil(gaps / 2);
const r = gaps - l;
return `${left}${" ".repeat(l)}${date}${" ".repeat(r)}${right}`;
// One quiet justified footer line, flex space-between so it stays flush at any
// wrap width. Mirrors manPage()'s footer.
function Footer({ date }: { date: string }) {
return (
<div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
<span>justhtml.sh</span>
<span>{date}</span>
<span>JUSTHTML.SH(1)</span>
</div>
);
}

export default async function ViewerNotFound() {
Expand Down Expand Up @@ -64,33 +66,32 @@ export default async function ViewerNotFound() {

return (
<main style={BODY}>
<pre style={{ fontWeight: 700, margin: "0 0 1.5rem" }}>JUSTHTML.SH(1)</pre>
<div style={{ fontWeight: 700, margin: "0 0 1.5rem" }}>JUSTHTML.SH(1)</div>
<h2
style={{
fontSize: 14,
fontWeight: 700,
margin: "1.6rem 0 0.3rem",
margin: "1.6rem 0 0.4rem",
textTransform: "uppercase",
letterSpacing: "0.03em",
letterSpacing: "0.05em",
}}
>
PRIVATE OR NOT FOUND
</h2>
<div style={{ paddingLeft: "3.5ch" }}>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
{`This document is private, or does not exist.

If you were given a link with a ?viewtoken=… on it, use that exact
link. The owner can rotate the token, which invalidates old links.

Was this shared with you? `}
<a href={signinHref}>Sign in</a>
{` with the email it was
shared to — if you have access, you'll land right back here.`}
</pre>
<p style={{ margin: "0 0 0.4rem" }}>This document is private, or does not exist.</p>
<p style={{ margin: "0 0 0.4rem" }}>
If you were given a link with a ?viewtoken=… on it, use that exact link.
The owner can rotate the token, which invalidates old links.
</p>
<p style={{ margin: 0 }}>
Was this shared with you? <a href={signinHref}>Sign in</a> with the
email it was shared to — if you have access, you'll land right back
here.
</p>
</div>
<footer style={{ marginTop: "2.5rem", color: "#666" }}>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{footerLine(date)}</pre>
<Footer date={date} />
</footer>
</main>
);
Expand Down
2 changes: 1 addition & 1 deletion app/d/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const dynamic = "force-dynamic";
//
// Token rules identical to /raw: owner session → email/domain grant → view token
// → public; otherwise the "private or does not exist" notice (no existence
// oracle). The root layout provides <html>/<body> + the monospace brand; these
// oracle). The root layout provides <html>/<body> + the sans reading face; these
// components render body content only.

type Props = {
Expand Down
Loading
Loading