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:
+71
@@ -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.
|
||||
|
||||
Executable
+37
@@ -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"
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user