Add a no-redesign semantic layer that makes native Gitea collaboration Hop-first

Hop-State: A_06FN45YJ09WC2QFGJM27VE8
Hop-Proposal: R_06FN45WVVTHC245S2BK503G
Hop-Task: T_06FN3MVGY3MT82ESQ89BND0
Hop-Attempt: AT_06FN3MVGY092BFA5MR9C7EG
This commit is contained in:
Hop
2026-07-11 09:52:00 -07:00
parent d9544b435b
commit 8115847b32
6 changed files with 394 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
# HopWeb Product
## Register
product
## Users
HopWeb is designed first for solo software developers who direct multiple AI
agents and need to understand, compare, and trust their work without manually
reconstructing intent from terminals and commit histories. The product should
also support engineering teams reviewing agent-produced outcomes, with clear
human ownership, attribution, policy, and evidence.
Users arrive with an outcome in mind. Their primary workflow is to state that
outcome, launch or supervise isolated attempts, intervene when necessary,
compare proposals, inspect evidence, and accept one verified result as shared
truth.
## Product Purpose
HopWeb is a prompt-native software forge. It combines universal Git
compatibility with a causal record of requested intent, agent attempts,
immutable checkpoints, validation evidence, reviewable proposals, and accepted
outcomes.
Success means a developer can answer four questions immediately: what was
requested, who or what attempted it, what exact result was verified, and which
outcome became accepted truth. Git files, commits, branches, and pull requests
remain available as compatibility views, but they do not dictate the primary
experience.
## Brand Personality
Focused, assured, and alive.
HopWeb should feel fast and command-ready like a serious developer instrument,
while remaining calm enough for careful review. Its personality comes from the
clarity of the state model and the visible motion of work through that model,
not decorative futurism or AI theatrics.
## Anti-references
- Not Gitea with an AI sidebar or a chatbot pasted onto repository pages.
- Not a generic SaaS dashboard assembled from interchangeable metric cards.
- Not a noisy cyberpunk developer tool with neon-on-black styling.
- Not an opaque agent console that asks users to trust summaries without exact
diffs and checkpoint-bound evidence.
- Not a GitHub imitation that merely renames pull requests and branches.
## Design Principles
1. **Intent before implementation.** Lead with the requested outcome and its
causal context before files, commits, or activity feeds.
2. **Supervision at a glance.** Make active attempts, interventions, failures,
and acceptance readiness legible without opening every detail view.
3. **Evidence is attached, not implied.** Every claim of success points to the
immutable checkpoint and checks that support it.
4. **Progressive Git compatibility.** Keep familiar Git affordances close, but
let Hop concepts own hierarchy, naming, and default navigation.
5. **Density without anxiety.** Favor keyboard speed and information density
while preserving rhythm, clear priority, and calm review states.
## Accessibility & Inclusion
Target WCAG 2.2 AA. Every workflow must be fully keyboard operable with visible
focus, semantic landmarks, descriptive labels, and non-color status cues.
Respect reduced-motion preferences and preserve comprehension when motion is
disabled. Status palettes must remain distinguishable for common color-vision
differences, and dense views must support comfortable zoom and narrow screens.
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
set -eu
env_file=${ENV_FILE:-.env}
if [ ! -f "$env_file" ]; then
echo "missing $env_file; copy .env.example and configure it first" >&2
exit 1
fi
docker compose --env-file "$env_file" up -d gitea
docker compose --env-file "$env_file" exec -T -u root gitea \
mkdir -p /data/gitea/templates/custom /data/gitea/public/assets/js
docker compose --env-file "$env_file" cp \
deploy/gitea/templates/custom/footer.tmpl \
gitea:/data/gitea/templates/custom/footer.tmpl
docker compose --env-file "$env_file" cp \
deploy/gitea/public/assets/js/hop-native.js \
gitea:/data/gitea/public/assets/js/hop-native.js
docker compose --env-file "$env_file" exec -T -u root gitea \
chown -R git:git /data/gitea/templates /data/gitea/public
docker compose --env-file "$env_file" exec -T -u git \
-e GITEA____APP_NAME=Hop gitea \
gitea --config /data/gitea/conf/app.ini config edit-ini --in-place --apply-env
docker compose --env-file "$env_file" restart gitea
attempt=0
until curl --fail --silent http://localhost:3000/api/healthz >/dev/null; do
attempt=$((attempt + 1))
if [ "$attempt" -ge 30 ]; then
echo "Gitea did not become healthy" >&2
exit 1
fi
sleep 1
done
echo "Hop-native Gitea semantics installed at http://localhost:3000"
+165
View File
@@ -0,0 +1,165 @@
(() => {
'use strict';
const exactLabels = new Map([
['Code', 'Files'],
['Issues', 'Tasks'],
['Pull Requests', 'Proposals'],
['Pull requests', 'Proposals'],
['Actions', 'Evidence'],
['Commits', 'Checkpoints'],
['Branches', 'Attempts'],
['New Issue', 'New Task'],
['Create Issue', 'Create Task'],
['New Pull Request', 'New Proposal'],
['Create Pull Request', 'Create Proposal'],
['Merge Pull Request', 'Accept Proposal'],
['Compare & pull request', 'Review as Proposal'],
]);
const phraseLabels = [
[/\bPull Requests\b/g, 'Proposals'],
[/\bPull requests\b/g, 'Proposals'],
[/\bpull requests\b/g, 'proposals'],
[/\bNew Issue\b/g, 'New Task'],
[/\bnew issue\b/g, 'new task'],
[/\bIssues\b/g, 'Tasks'],
[/\bissues\b/g, 'tasks'],
[/\bActions\b/g, 'Evidence'],
[/\bCommits\b/g, 'Checkpoints'],
[/\bBranches\b/g, 'Attempts'],
];
const semanticRoots = [
'#navbar',
'.secondary-nav',
'[role="main"] .ui.header',
'[role="main"] h1',
'[role="main"] h2',
'[role="main"] h3',
'[role="main"] .ui.button',
'[role="main"] .menu',
'[role="main"] .breadcrumb',
'[role="main"] label',
].join(',');
function replaceExactText(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
for (const node of nodes) {
const original = node.nodeValue;
const trimmed = original.trim();
const replacement = exactLabels.get(trimmed);
if (!replacement) continue;
node.nodeValue = original.replace(trimmed, replacement);
}
}
function replacePhrases(value) {
let result = value;
for (const [pattern, replacement] of phraseLabels) {
result = result.replace(pattern, replacement);
}
return result;
}
function replaceAccessibleLabels(root) {
const attributes = ['aria-label', 'data-tooltip-content', 'placeholder', 'title'];
for (const element of root.querySelectorAll('[aria-label], [data-tooltip-content], [placeholder], [title]')) {
for (const attribute of attributes) {
const value = element.getAttribute(attribute);
if (!value) continue;
const replacement = replacePhrases(value);
if (replacement !== value) element.setAttribute(attribute, replacement);
}
}
}
function repoNavigation() {
return document.querySelector('.secondary-nav .overflow-menu-items');
}
function navigationLink(suffix) {
const navigation = repoNavigation();
if (!navigation) return null;
return [...navigation.querySelectorAll(':scope > a.item')].find((link) => {
const path = new URL(link.href, window.location.origin).pathname.replace(/\/$/, '');
return path.endsWith(suffix);
}) || null;
}
function workflowLink(source, label) {
if (!source) return null;
const link = document.createElement('a');
link.className = 'flex-text-block muted';
link.href = source.href;
link.setAttribute('aria-label', label);
const icon = source.querySelector('svg');
if (icon) link.append(icon.cloneNode(true));
link.append(document.createTextNode(` ${label}`));
const count = source.querySelector('.ui.label');
if (count) link.append(count.cloneNode(true));
return link;
}
function addWorkflowSummary() {
const sidebar = document.querySelector('.repo-home-sidebar-top');
if (!sidebar || sidebar.querySelector('.hop-native-workflow')) return;
const links = [
workflowLink(navigationLink('/issues'), 'Tasks'),
workflowLink(navigationLink('/pulls'), 'Proposals'),
workflowLink(navigationLink('/actions'), 'Evidence'),
].filter(Boolean);
if (!links.length) return;
const list = document.createElement('div');
list.className = 'flex-list hop-native-workflow';
const item = document.createElement('div');
item.className = 'flex-item';
const main = document.createElement('div');
main.className = 'flex-item-main';
const title = document.createElement('div');
title.className = 'flex-item-title';
title.textContent = 'Hop workflow';
const body = document.createElement('div');
body.className = 'flex-item-body tw-text-15';
const linkList = document.createElement('div');
linkList.className = 'tw-flex tw-flex-col tw-gap-2 tw-mt-2';
for (const link of links) linkList.append(link);
body.append(linkList);
main.append(title, body);
item.append(main);
list.append(item);
sidebar.prepend(list);
}
function applyHopSemantics() {
for (const root of document.querySelectorAll(semanticRoots)) replaceExactText(root);
replaceAccessibleLabels(document);
addWorkflowSummary();
const title = replacePhrases(document.title);
if (title !== document.title) document.title = title;
document.documentElement.dataset.hopNative = 'true';
}
let queued = false;
function queueApply() {
if (queued) return;
queued = true;
window.requestAnimationFrame(() => {
queued = false;
applyHopSemantics();
});
}
document.addEventListener('DOMContentLoaded', queueApply);
document.addEventListener('htmx:afterSwap', queueApply);
new MutationObserver(queueApply).observe(document.documentElement, {childList: true, subtree: true});
})();
@@ -0,0 +1,2 @@
<script src="{{AssetUrlPrefix}}/js/hop-native.js?v=1"></script>
@@ -0,0 +1,64 @@
# ADR 0002: Preserve Gitea's design and replace its collaboration semantics
- Status: Accepted
- Date: 2026-07-11
## Context
HopWeb needs to become Hop-native without spending early product effort on a
new visual shell or maintaining broad Gitea template forks. Gitea already has a
coherent, accessible component system and familiar repository navigation.
The first product distinction is conceptual rather than visual. Hop users work
with tasks, attempts, checkpoints, proposals, evidence, and accepted outcomes.
Gitea exposes closely related infrastructure as issues, branches, commits, pull
requests, Actions, and merges.
## Decision
Keep Gitea's existing layout, typography, color, spacing, icons, responsive
behavior, and component states unchanged.
Load a small same-origin semantic adapter through Gitea's supported custom
footer template. The adapter changes visible labels and matching accessibility
metadata while preserving the underlying routes and behavior:
| Gitea element | Hop meaning |
| --- | --- |
| Code | Files |
| Issues | Tasks |
| Branches | Attempts |
| Commits | Checkpoints |
| Pull Requests | Proposals |
| Actions | Evidence |
| Merge Pull Request | Accept Proposal |
On repository home pages, add a compact `Hop workflow` summary using Gitea's
existing sidebar classes and the live navigation links and counts already
rendered by Gitea. Do not add custom CSS.
Set the application name to `Hop`, while retaining Gitea attribution and its
standard administrative and Git compatibility surfaces.
## Consequences
- Existing Gitea behavior, responsiveness, themes, and accessibility remain
available.
- The first Hop experience ships without a deep template fork.
- URLs and APIs remain Gitea-compatible even when their visible names change.
- DOM selectors and English labels are version-sensitive and require a smoke
test when upgrading Gitea.
- As Hop gains distinct task and state behavior, individual semantic elements
can move from adapted Gitea routes to native server-backed routes without a
simultaneous visual redesign.
## Guardrails
- Do not add CSS to the semantic adapter.
- Never rename an element unless its behavior is meaningfully compatible with
the corresponding Hop concept.
- Keep accessible names and tooltips synchronized with visible labels.
- Scope text replacement to navigation, headings, menus, labels, breadcrumbs,
and buttons. Never rewrite repository content or user-authored prose.
- Verify the adapter against the pinned Gitea version before every upgrade.
+55
View File
@@ -0,0 +1,55 @@
# Hop-native Gitea semantics
HopWeb keeps Gitea's existing interface design and changes the meaning of its
collaboration elements. No custom stylesheet, theme, component library, or
layout override is installed.
## Install
From the project root:
```sh
./deploy/gitea/install-hop-native.sh
```
Set `ENV_FILE` when the Compose environment is stored elsewhere:
```sh
ENV_FILE=/path/to/environment ./deploy/gitea/install-hop-native.sh
```
The installer copies the adapter into Gitea's persistent custom directory,
sets the application name to `Hop`, restarts Gitea, and waits for a healthy
response. Re-run it after editing the adapter or replacing the Gitea volume.
## Semantic mapping
| Native route and behavior | Hop label |
| --- | --- |
| Repository code | Files |
| Issues | Tasks |
| Branches | Attempts |
| Commits | Checkpoints |
| Pull requests | Proposals |
| Actions | Evidence |
| Merge pull request | Accept proposal |
The adapter updates visible text, document titles, form placeholders,
tooltips, and ARIA labels. It scopes replacements to Gitea navigation,
headings, menus, breadcrumbs, labels, and buttons, so repository content and
user-authored prose remain unchanged.
Repository home pages also receive a `Hop workflow` group in the existing
Gitea sidebar. It uses Gitea's own classes, links, icons, and live counts.
## Upgrade check
After changing the pinned Gitea version:
1. run the installer;
2. confirm Tasks, Proposals, and Evidence in repository navigation;
3. confirm Attempts and Checkpoints in the Files view;
4. confirm proposal creation and acceptance labels;
5. inspect browser console errors; and
6. test a narrow viewport and keyboard navigation.