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
33 changes: 29 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GitHub theme for Gitea 1.27.3 — Architecture
# GitHub theme for Gitea 1.27.x and 28.x — Architecture

Goal: a self-hosted Gitea 1.27.3 that a daily github.com user needs a second look to tell apart from github.com,
in light and dark. Every color, size, radius, shadow, type and motion value comes from **Primer Primitives**, every
Expand Down Expand Up @@ -30,7 +30,7 @@ Environment facts: `docs/CONTEXT.md`. Gitea source research (with file:line cita
| `@primer/primitives` | **11.10.0** | all tokens (`build/gen-tokens.mjs` → `src/tokens/generated/`) |
| `@primer/octicons` | **19.38.0** | icon source (Gitea 1.27.3 bundles 19.28.1) |
| `@primer/css` | **22.3.2** | reference only for component specs (not shipped) |
| Gitea | **1.27.3** | source at `../gitea-src-1.27.3` (tag v1.27.3) |
| Gitea | **1.27.3** and **28.1.0** | sources at `../gitea-src-1.27.3`, `../gitea-src-28.1.0`; one package for both (§7 "Two Gitea versions") |

Recorded again in `docs/STATUS.json → pinned`.

Expand Down Expand Up @@ -182,12 +182,36 @@ keep the non-GitHub output byte-identical.
| `templates/base/head_style.tmpl` | Cascade layering (§3) + cache-busting `?github_revision=`. Shared with the Modern theme's existing override: our branch is additive, the else-branch is untouched. | `github-*` themes only | integrator, wave 0 |
| `templates/custom/footer.tmpl` | Primer Dialog close button (request OV-4): Gitea's modals mostly have no `×`; a 12-line inline script (CSP nonce) prepends `button.close.inside` to every `.ui.modal` (Fomantic closes on `> .close`). `custom/footer` is Gitea's empty extension hook, so no upstream markup is replaced. Install is done by the orchestrator (docs/requests/ORCHESTRATOR.md ORC-1). | `github-*` themes only (renders nothing otherwise) | integrator, wave 2 |
| `templates/user/settings/layout_head.tmpl` | github.com settings page header (48px avatar, name, subtitle, profile button; request SA-4): needs the signed-in user's data, CSS cannot create it. Upstream 1.27.3 file + one trimmed `{{- if … }}` block; copy text is Gitea's `your_settings` / `your_profile` locale keys (Gitea has no "Your personal account" string). Styled by `src/pages/settings-admin/header.css`. Install: docs/requests/ORCHESTRATOR.md ORC-3. | `github-*` themes only, pages whose pageClass contains `settings` (other themes: upstream bytes) | integrator, wave 3 |
| `templates/base/head_navbar.tmpl` + `templates/custom/gh_head_navbar.tmpl` | github.com AppHeader (FG-007, judges' #1 tell, impact 130) and the slim auth header (FG-063): hamburger drawer with Gitea's links, context crumbs, search form, Issues/PRs icon buttons — the crumbs, search and cross-container moves are not CSS-reachable (docs/final-gate/issues.md "Header decision"). The override is `{{if github-*}}{{template "custom/gh_head_navbar" .}}{{else}}` + upstream 1.27.3 verbatim + `{{end}}` (no trailing newline). All JS hooks kept, no JavaScript added (native `<details>`). Install: ORCHESTRATOR.md ORC-5. Styled by navigation (`.gh-app-header*`) and pages/auth (`.gh-app-header--auth`). | `github-*` themes only (else-branch = upstream bytes) | integrator, final-gate #1 (pending install) |
| `templates/base/head_navbar.tmpl` + `templates/custom/gh_head_navbar.tmpl` | github.com AppHeader (FG-007, judges' #1 tell, impact 130) and the slim auth header (FG-063): hamburger drawer with Gitea's links, context crumbs, search form, Issues/PRs icon buttons — the crumbs, search and cross-container moves are not CSS-reachable (docs/final-gate/issues.md "Header decision"). The override is `{{if github-*}}{{template "custom/gh_head_navbar" .}}{{else if AppVer 1.*}}` + upstream 1.27.3 verbatim + `{{else}}` + upstream 28.1.0 verbatim + `{{end}}` (no trailing newline). All JS hooks kept, no JavaScript added (native `<details>`). Install: ORCHESTRATOR.md ORC-5. Styled by navigation (`.gh-app-header*`) and pages/auth (`.gh-app-header--auth`). | `github-*` themes only (else-branch = upstream bytes) | integrator, final-gate #1 (pending install) |
| `templates/repo/commits_list.tmpl` | "Commits on <date>" day groups (FG-019, impact 35): per-row date state, CSS cannot insert rows. One `tr.gh-commit-day` per committer day (template funcs only: `When.Format`, `DateUtils.ParseLegacy/AbsoluteShort`); date-only label (no "Commits on" locale key). ORC-6. Styled by pages/repo. | `github-*` themes, not the wiki revision list | integrator, final-gate #1 (pending install) |
| `templates/repo/view_file.tmpl` + `templates/repo/blame.tmpl` | Preview \| Code \| Blame SegmentedControl (FG-021, impact 29): the "Code" segment and moving Blame out of the button group need markup; existing links only, keys `preview` / `repo.code` / `repo.blame`. README box untouched. ORC-7. Styled by code. | `github-*` themes only | integrator, final-gate #1 (pending install) |
| `templates/repo/wiki/view.tmpl` | wiki "Pages (N)" Box + clone input (FG-022, impact 27): `.Pages` and the clone link are in ctx on every wiki view (routers/web/repo/wiki.go renderViewPage); the Fomantic dropdown and tippy-only clone popup cannot be turned into a static sidebar with CSS. No filter input (would need JS) and no "Clone this wiki locally" heading (no locale key). ORC-8. Styled by pages/repo. | `github-*` themes only | integrator, final-gate #1 (pending install) |
| `templates/repo/issue/list.tmpl` | issues / PRs left NavList + title (FG-017, impact 45): links to Gitea's existing `?type=` filters, Milestones, Labels (keys `repo.issues.filter_type.*`); CSS cannot create a sidebar column. Issue/PR list only — the Labels and Milestones pages were not given it (cap). ORC-9. Loop 2 (FG2-027): the NavList is rendered on the issue list only, not the PR list (github.com) — project copy edited, install ORC-13. Styled by pages/issues-prs. | `github-*` themes only | integrator, final-gate #1; FG2-027 edit loop 2 (install pending) |

**Two Gitea versions (1.27.x and 28.x, one package).** Each template that differs between 1.27.3 and 28.1.0 starts with
`{{$gitea1 := StringUtils.HasPrefix AppVer "1."}}` (no output) and branches only where upstream differs, so a non-github
theme gets each version's upstream bytes (checked: anonymous HTML of all routes with vs. without the templates, both
versions, differs only by the documented `hidden` "N Commits" link). Two rules learned the hard way:
- Fields and methods are resolved at execution, so an untaken branch may name `.Repository.CanEnableEditor` (1.27) or
`CanContentChange` (28); use `and`/`or` (lazy since Go 1.18) or `{{if $gitea1}}` around them.
- `{{template "x"}}` is checked by html/template's escaper even in a branch that never runs: an undefined name breaks the
whole page. Hence `templates/base/head_impersonate_banner.tmpl` (28.1.0 verbatim) ships so the name exists on 1.27.
CSS: where 28 renamed or restructured markup, the 28 selectors are added next to the 1.27 ones and commented "28.x"
(profile sidebar `#profile-avatar-card.segment`, commit header and its "N files changed" footer row, `.site-banner-*`,
diff kebab, feed excerpt, `.dropzone` without `.ui`, release header `.flex-left-right`, notifications toolbar
`.flex-text-block`, Actions sidebar with a second "Management" menu, deploy key / token panels). The wiki view template
keeps the `view` class (dropped in 28) for github-* themes. Features only 28 has are styled to the matching Primer
component, not left on Gitea's defaults: toasts per level (Primer Toast), site + impersonation banners (flash-full),
repo switcher (SelectPanel on the AppHeader repo crumb, as on github.com; the repo-header copy is hidden), Actions job queue (PageLayout pane + DataTable), artifact preview (NavList file tree),
diff file filter + extension menu (TextInput + IconButton + ActionMenu), audit log (filter row + Box table), deploy
tokens (Box rows, Blankslate), "Mark this page as read". Verified 2026-10-12 on 1.27.3 and 28.1.0: light, dark, 390px;
1.27 pixel-diffed against a pre-change capture of every route (only live data differs). Not seen with real data: the
`diff-line-truncated` label (lines over 5000 characters) and a bot account's label (both plain Primer Labels).
When 1.27 support ends, delete the `$gitea1` branches and the 1.27 selectors.

Pitfall: `X > :is(A > B)` does not mean "B under A under X" — the `:is()` element must match X's child AND have an
A parent, so it silently matches nothing. List whole paths instead (`:has(> … > A > B, > … > C)`).

Final gate #1 template requests REJECTED (§7 criteria: a impact, b data in ctx, c existing locale keys, d byte-identical
branch, e ≤ 8 new overrides; the seven files above are 7 of 8): FG-024 branches table (c: no keys for Updated / Check
status / Behind|Ahead column headers; kebab needs a full row rewrite; e), FG-025 tree-pane branch picker and FG-044
Expand Down Expand Up @@ -246,7 +270,8 @@ column colors, repo units, federation, wiki clone box, etc.) get the **closest P

## 10. Budget

- ≤ 300 KB per theme file minified (build fails otherwise); gate for every loop: ≤ 295 KB. Current sizes in `docs/STATUS.json`.
- ≤ 340 KB per theme file minified (build fails otherwise); gate for every loop: ≤ 335 KB. Raised from 300 / 295 KB on
2026-10-12 (user decision) to style Gitea 28's new UI in the same single file for 1.27 and 28 (gzip at 300 KB: ~57 KB). Current sizes in `docs/STATUS.json`.
Loop 1 (after wave L1, 2026-09-30): the wave added 63.6 KB of flat CSS (auto 283,706 → 347,273 B; per-layer growth in
STATUS.json → budget.loop1); the lossless prefix nesting (§9) brings the files to auto 295,880 B (288.9 KB), light
290,683 B, dark 291,734 B — no rules were deleted.
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build & release](https://github.com/mechiland/gitea-theme-github/actions/workflows/release.yml/badge.svg)](https://github.com/mechiland/gitea-theme-github/actions/workflows/release.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

A GitHub look-and-feel theme for **Gitea 1.27.3**, in light, dark and auto (follows the OS color scheme).
A GitHub look-and-feel theme for **Gitea 1.27.x and 28.x** (tested on 1.27.3 and 28.1.0; one package for both), in light, dark and auto (follows the OS color scheme).

It is built from GitHub's own open-source design system, not by recolouring Gitea:
- every color, size, radius, shadow, type and motion value comes from [Primer Primitives](https://primer.style/product/primitives/) (`@primer/primitives` 11.10.0);
Expand Down Expand Up @@ -48,7 +48,7 @@ The theme is a set of files for Gitea's custom directory (`CUSTOM_PATH`, `$GITEA

### 1. Download the release package

Take `gitea-theme-github-<version>-gitea1.27.3.tar.gz` (or `.zip`) from [Releases](https://github.com/mechiland/gitea-theme-github/releases), or the `gitea-theme-github` artifact of the latest [CI run](https://github.com/mechiland/gitea-theme-github/actions).
Take `gitea-theme-github-<version>-gitea1.27-28.tar.gz` (or `.zip`) from [Releases](https://github.com/mechiland/gitea-theme-github/releases), or the `gitea-theme-github` artifact of the latest [CI run](https://github.com/mechiland/gitea-theme-github/actions).

It contains:

Expand All @@ -65,13 +65,13 @@ INSTALL.md, LICENSE, NOTICE
**Docker (docker compose, volume `./gitea:/data`):**

```bash
tar -xzf gitea-theme-github-*-gitea1.27.3.tar.gz -C ./gitea/gitea
tar -xzf gitea-theme-github-*-gitea1.27-28.tar.gz -C ./gitea/gitea
```

**Binary / systemd:**

```bash
sudo tar -xzf gitea-theme-github-*-gitea1.27.3.tar.gz -C /var/lib/gitea/custom
sudo tar -xzf gitea-theme-github-*-gitea1.27-28.tar.gz -C /var/lib/gitea/custom
sudo chown -R git:git /var/lib/gitea/custom
```

Expand All @@ -90,7 +90,7 @@ With the Docker image, you can set the same values with environment variables in
```yaml
services:
server:
image: docker.gitea.com/gitea:1.27.3
image: docker.gitea.com/gitea:28.1.0 # or 1.27.x
environment:
- GITEA__ui__THEMES=gitea-auto,gitea-light,gitea-dark,github-auto,github-light,github-dark
- GITEA__ui__DEFAULT_THEME=github-auto
Expand Down Expand Up @@ -123,8 +123,9 @@ Extracting never deletes files. If the release notes say a template was removed,

### Notes

- **Gitea version:** the templates are copies of Gitea **1.27.3** templates with added GitHub-only blocks. Re-check them when you upgrade Gitea. The CSS alone still works without the templates, but you lose the cascade layering (see [ARCHITECTURE.md §3](ARCHITECTURE.md)), the GitHub-style header and a few page structures.
- **If you already override templates:** the package ships these templates (all under `templates/`): `base/head_style.tmpl`, `base/head_navbar.tmpl`, `custom/footer.tmpl`, `custom/gh_head_navbar.tmpl`, `repo/blame.tmpl`, `repo/commits_list.tmpl`, `repo/issue/list.tmpl`, `repo/view_file.tmpl`, `repo/view_list.tmpl`, `repo/wiki/view.tmpl`, `user/settings/layout_head.tmpl`. If you override one of them yourself, extracting the package replaces your copy. Back it up first and merge the GitHub additions by hand: the `{{if StringUtils.HasPrefix … "github-"}}` blocks, and in `repo/view_list.tmpl` the `hidden` "N Commits" link (`a.m-commit-count`).
- **Gitea version:** the templates are copies of Gitea templates with added GitHub-only blocks. Where Gitea 1.27 and 28 differ, a template carries both variants and picks one from the running Gitea version (`AppVer`), so you can install the package before upgrading Gitea from 1.27 to 28 and keep it afterwards. Packages up to v0.1.2 only work on 1.27.x: on 28.x the repository home and file pages fail to render. Re-check the templates when a new Gitea major comes out. The CSS alone still works without the templates, but you lose the cascade layering (see [ARCHITECTURE.md §3](ARCHITECTURE.md)), the GitHub-style header and a few page structures.
- **Footer:** shows the running Gitea version next to "Powered by Gitea" (Gitea's own `[other] SHOW_FOOTER_VERSION`, on by default; admins get a link to the site configuration). Set it to `false` to hide it.
- **If you already override templates:** the package ships these templates (all under `templates/`): `base/head_style.tmpl`, `base/head_navbar.tmpl`, `base/head_impersonate_banner.tmpl` (Gitea 28.1.0's file, needed so 1.27 can load the navbar), `custom/footer.tmpl`, `custom/gh_head_navbar.tmpl`, `repo/blame.tmpl`, `repo/commits_list.tmpl`, `repo/issue/list.tmpl`, `repo/view_file.tmpl`, `repo/view_list.tmpl`, `repo/wiki/view.tmpl`, `user/settings/layout_head.tmpl`. If you override one of them yourself, extracting the package replaces your copy. Back it up first and merge the GitHub additions by hand: the `{{if StringUtils.HasPrefix … "github-"}}` blocks, and in `repo/view_list.tmpl` the `hidden` "N Commits" link (`a.m-commit-count`).
- **Without the templates:** the repo home falls back to Gitea's Commits / Branches / Tags bar for the commit-history link. With `repo/view_list.tmpl`, "N Commits" sits at the right of the latest-commit row, as on github.com.
- **Icons are global:** the SVG icons replace Gitea's icons for every theme. They depict the same things, drawn as Octicons.
- **Browser support:** Chrome/Edge 112+, Safari 16.5+, Firefox 121+. The theme uses `@layer`, `:has()` and CSS nesting.
Expand All @@ -137,7 +138,7 @@ git clone https://github.com/mechiland/gitea-theme-github && cd gitea-theme-gith
npm ci
npm run lint # no literal colors outside the token layer, type/radius/shadow from tokens, selector ownership
npm run build # → dist/theme-github-{auto,light,dark}.css (budget: ≤ 300 KB each, minified)
npm run package # → dist/gitea-theme-github-<version>-gitea1.27.3.{zip,tar.gz} (the drop-in package above)
npm run package # → dist/gitea-theme-github-<version>-gitea1.27-28.{zip,tar.gz} (the drop-in package above)
```

Where things live:
Expand Down
2 changes: 1 addition & 1 deletion build/folders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export const THEMES = {
'github-dark': {display: 'GitHub Dark', scheme: 'dark'},
'github-auto': {display: 'GitHub', scheme: 'auto'},
};
export const BUDGET_BYTES = 300 * 1024;
export const BUDGET_BYTES = 340 * 1024; // 300 KB until Gitea 28 support (2026-10-12, user decision); loop gate 335 KB
18 changes: 11 additions & 7 deletions build/package.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
// Builds a drop-in release package for Gitea's CUSTOM_PATH from an existing `npm run build` output.
//
// node build/package.mjs → dist/gitea-theme-github-<version>-gitea<ver>.{zip,tar.gz} + dist/package/
// node build/package.mjs → dist/gitea-theme-github-<version>-gitea<GITEA_SUPPORTED>.{zip,tar.gz} + dist/package/
//
// Layout (extract into $GITEA_CUSTOM, e.g. /data/gitea in the official Docker image):
// public/assets/css/theme-github-{auto,light,dark}.css
// public/assets/img/svg/*.svg Octicons 19.38 upgrade + non-Octicon replacements (global, all themes)
// templates/** github-* branches only; other themes render Gitea's upstream bytes
// (repo/view_list.tmpl: a `hidden` link for other themes)
//
// templates/base/head_style.tmpl: the package pins the else-branch to Gitea 1.27.3's upstream two lines (a local install may
// carry another theme's cache key there) and stamps the build revision for cache busting.
// templates/base/head_style.tmpl: the package pins the else-branch to Gitea's upstream two lines (identical in 1.27.3 and
// 28.1.0; a local install may carry another theme's cache key there) and stamps the build revision for cache busting.
// One package serves Gitea 1.27.x and 28.x: templates branch on `StringUtils.HasPrefix AppVer "1."` where the two differ.
import fs from 'node:fs';
import path from 'node:path';
import {execFileSync} from 'node:child_process';
import {ROOT, DIST, SRC, THEMES} from './folders.mjs';

const GITEA_VERSION = '1.27.3';
const GITEA_SUPPORTED = '1.27-28'; // tested on 1.27.3 and 28.1.0
const GITEA_TESTED = '1.27.3 and 28.1.0';
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
const report = JSON.parse(fs.readFileSync(path.join(DIST, 'build-report.json'), 'utf8'));
const name = `gitea-theme-github-${pkg.version}-gitea${GITEA_VERSION}`;
const name = `gitea-theme-github-${pkg.version}-gitea${GITEA_SUPPORTED}`;
const out = path.join(DIST, 'package', 'gitea-theme-github');

fs.rmSync(path.join(DIST, 'package'), {recursive: true, force: true});
Expand Down Expand Up @@ -53,7 +55,7 @@ for (const rel of fs.readdirSync(tplRoot, {recursive: true}).filter((f) => f.end
}

for (const f of ['LICENSE', 'NOTICE']) copy(path.join(ROOT, f), path.join(out, f));
fs.writeFileSync(path.join(out, 'INSTALL.md'), `# gitea-theme-github ${pkg.version} (for Gitea ${GITEA_VERSION})
fs.writeFileSync(path.join(out, 'INSTALL.md'), `# gitea-theme-github ${pkg.version} (for Gitea 1.27.x and 28.x, tested on ${GITEA_TESTED})

1. Copy the contents of this folder into Gitea's custom directory (\`$GITEA_CUSTOM\`, \`/data/gitea\` in the official
Docker image), keeping the paths: \`public/…\` and \`templates/…\`.
Expand All @@ -64,7 +66,9 @@ fs.writeFileSync(path.join(out, 'INSTALL.md'), `# gitea-theme-github ${pkg.versi
4. Pick "GitHub" in Settings → Appearance.

Theme files: ${Object.entries(report.themes).map(([n, t]) => `${n} ${t.kb} KB`).join(', ')} · build ${report.revision}.
The templates are copies of Gitea ${GITEA_VERSION} templates with added github-only blocks; re-check them when upgrading Gitea.
The templates are copies of Gitea's templates with added github-only blocks. Where Gitea 1.27 and 28 differ they carry both
versions (picked at runtime from Gitea's version), so the same package keeps working across a 1.27 → 28 upgrade.
templates/base/head_impersonate_banner.tmpl is Gitea 28.1.0's file verbatim: on 1.27.x it only has to exist.
If you already override one of these templates, merge the github-* blocks by hand.
templates/repo/view_list.tmpl adds the "N Commits" link to the latest-commit header (hidden for other themes).
The SVG files replace Gitea's icons for every theme (same meaning, Octicon drawings).
Expand Down
2 changes: 1 addition & 1 deletion docs/STATUS.json
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@
"bytes": 291734,
"kb": 284.9
},
"budgetKb": 300,
"budgetKb": 340,
"gateKb": 295,
"targetKb": 285,
"withinBudget": true,
Expand Down
Loading