From 8115847b32d0e9beced1ebbe865701e85a76a085 Mon Sep 17 00:00:00 2001 From: Hop Date: Sat, 11 Jul 2026 09:52:00 -0700 Subject: [PATCH] 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 --- PRODUCT.md | 71 ++++++++ deploy/gitea/install-hop-native.sh | 37 +++++ deploy/gitea/public/assets/js/hop-native.js | 165 +++++++++++++++++++ deploy/gitea/templates/custom/footer.tmpl | 2 + docs/adr/0002-gitea-native-semantic-layer.md | 64 +++++++ docs/gitea-hop-native.md | 55 +++++++ 6 files changed, 394 insertions(+) create mode 100644 PRODUCT.md create mode 100755 deploy/gitea/install-hop-native.sh create mode 100644 deploy/gitea/public/assets/js/hop-native.js create mode 100644 deploy/gitea/templates/custom/footer.tmpl create mode 100644 docs/adr/0002-gitea-native-semantic-layer.md create mode 100644 docs/gitea-hop-native.md diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..ef4f11d --- /dev/null +++ b/PRODUCT.md @@ -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. + diff --git a/deploy/gitea/install-hop-native.sh b/deploy/gitea/install-hop-native.sh new file mode 100755 index 0000000..0f4804b --- /dev/null +++ b/deploy/gitea/install-hop-native.sh @@ -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" diff --git a/deploy/gitea/public/assets/js/hop-native.js b/deploy/gitea/public/assets/js/hop-native.js new file mode 100644 index 0000000..0c6da07 --- /dev/null +++ b/deploy/gitea/public/assets/js/hop-native.js @@ -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}); +})(); diff --git a/deploy/gitea/templates/custom/footer.tmpl b/deploy/gitea/templates/custom/footer.tmpl new file mode 100644 index 0000000..b0d30d0 --- /dev/null +++ b/deploy/gitea/templates/custom/footer.tmpl @@ -0,0 +1,2 @@ + + diff --git a/docs/adr/0002-gitea-native-semantic-layer.md b/docs/adr/0002-gitea-native-semantic-layer.md new file mode 100644 index 0000000..d6e5c6a --- /dev/null +++ b/docs/adr/0002-gitea-native-semantic-layer.md @@ -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. + diff --git a/docs/gitea-hop-native.md b/docs/gitea-hop-native.md new file mode 100644 index 0000000..3f6a646 --- /dev/null +++ b/docs/gitea-hop-native.md @@ -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. +