Chrome extension
extension/ is a Chrome Manifest V3 extension built with WXT and React 19. It puts a practice review report into the provider's own page.
The report shows what Hephaestus recorded about the pull request, merge request, or issue open in this tab.
It includes the evidence and feedback delivery results. It is desktop Chrome only. Chrome on Android and
iOS does not run desktop extensions.
The extension is a place to read context, not a feedback destination. It never delivers feedback and never
calls GET /practices/feedback/practice-page, which marks the caller's own feedback delivered as a side
effect. The user guide is the product description and
the admin guide the operator's. This page is how it is built.
Architecture
| Surface | Runs as | Holds | May |
|---|---|---|---|
| Service worker | extension origin | instance URL, credentials, generated client, one intent | call the API, refresh, reconcile content scripts, open the confirmation window |
| Content script | isolated world on a granted provider origin | nothing | recognize a supported route, insert one generic report host with the report frame |
Report frame (inline.html) | extension origin, framed by the provider page | the current view | read about its own tab's work. Ask for the confirmation window. Link to the web app |
Confirmation window (action.html) | extension origin, top-level, not web-accessible | one pending action's preview | confirm or discard the one intent bound to its tab |
| Options page | extension origin, unframeable | settings view | instance URL, sign-in, per-origin grants, sign-out |
| Hephaestus web app | configured web origin, new top-level tab | its own web session | approve or reject feedback. Everything else about a review |
The worker owns every credential
Tokens live in chrome.storage.session with its access level set to TRUSTED_CONTEXTS, so content
scripts cannot read them and Chrome drops them when it closes. Only the worker uses the generated
client, with credentials: "omit" and a bearer token. It single-flights refresh so two views never present the same refresh secret.
On sign-out, instance change, or site revocation, it increments a persisted session generation.
Thus, it drops late responses instead of rendering them.
Views talk to the worker through one narrow RPC union validated on arrival. The worker checks
sender.id, and for view messages that sender.url is one of its own pages. It derives the work's
identity from the current top-level URL returned by chrome.tabs.get for the Chrome-attested sender
tab, never from a URL in the message. It rechecks the canonical work after asynchronous reads and
rejects results if navigation changed the work. There is no generic
fetch-this-path message, no externally_connectable, no cookies permission.
What the provider page can and cannot see
The provider's scripts can read anything written into its DOM. The isolated world separates JavaScript heaps, not the document. A shadow root isolates styles, not confidential data. Thus, the boundary protects data and actions, not the extension's presence:
- Authenticated data stays out of the provider DOM. The content script inserts one generic report host. It contains no state, count, name, or workspace. Its collapsed height is identical in every state. The report renders inside the extension-origin frame, whose document the page cannot read.
- Review changes are confirmed in the extension's own window. The provider could overlay the frame and misdirect a real click.
Although it cannot read the frame, the frame sends no mutation.
It asks the worker to open
action.html. The worker records one pending intent bound to session generation, source tab, exact work, and the new window's tab. It creates that tab blank, binds it, then sends it to its actionable address. The window rereads the intent through the worker, which resolves everything again with the server. Confirmation consumes the intent in one serialized storage step before the single request leaves. Thus, a double click, reload, lost reply, or restarted worker never sends it twice. A request without an answer appears as unknown and is never retried. A200 REFUSEDis the server's answer, shown unchanged. Approval or rejection of feedback stays in the web app, which owns the complete decision package. - Everything the page passes is untrusted. The frame URL carries an opaque per-mounting token, provider, and validated theme enum. For a list-row preview, it also carries the row's work address. None of this can open the report or select a workspace. The worker keeps the reader's open-state and workspace choices per tab, work, and session generation. Only the frame can set them. A preview starts closed, regardless of how the page created it.
- A list row is a selector, not an authority. The page can frame the preview itself and name any row.
The worker accepts the row address only as canonical work in the repository and kind shown by the tab.
It reads the tab with
tabs.getand rereads it after every await. Another list, filter, or page makes the result stale. A row reaches only the line and reader's own comments (get-context,get-work-feedback). Results, admin records, and review changes belong to the work page. - The page can still observe the extension. It can see that the report exists and its frame's size. The page and the frame exchange only readiness, height and theme messages. Nothing here hides that the user runs the extension or when they open the report.
Where the report goes
The report is a section of the work's own content column.
It is never a sticky header, grid track, fake timeline item, or floating substitute.
A page without a known slot gets no report.
src/content/anchors.ts owns the selectors. We checked each selector read-only on a real signed-in page.
We also checked the GitHub files view signed out:
| Page | Slot |
|---|---|
| GitHub pull request, conversation | inside .js-discussion, after the pullRequestsConversationsRoute.Body partial, before the timeline |
| GitHub pull request, changes | before [data-testid=progressive-diffs-list] in [data-testid=diff-content] |
| GitHub pull request, files view | before #files > .js-diff-progressive-container (served e.g. to signed-out visitors for /files) |
| GitHub issue | after [data-testid=issue-viewer-issue-container], before the activity |
| GitLab merge request, overview | after .issuable-discussion > #widget-state, before its activity (never the global #notes) |
| GitLab merge request, changes | before .rd-app-diffs-list in the Rapid Diffs section.rd-app-content |
| GitLab work item | first inside [data-testid=detail-layout-activity], adding no grid child |
Providers keep hidden tab panes in the page, so only a visible slot counts, including panes hidden
with content-visibility as GitLab uses for inactive views. Commits, checks, pipelines and
repository pages have none. e2e/provider-pages.ts mirrors these shapes.
On a repository's list, the provider's own title hook identifies rows, never an arbitrary list-item link. A row can carry other links, and a page can hold other lists. GitLab's work items put a full-row overlay link first.
| List | Row and title | Preview |
|---|---|---|
GitHub /pulls | ul[data-listview-component=items-list] > li, h3 > a[data-testid=listitem-title-link] | last in the row, across its grid |
GitHub /issues | ul[data-listview-component=items-list] li (virtualized), h3 > a[data-testid=issue-pr-title-link] | last in the row, across its grid |
GitLab /-/merge_requests, /-/issues, /-/work_items | li[data-testid=issuable-container], a[data-testid=issuable-title-link] in .issuable-main-info | last in .issuable-main-info |
The inspect control goes immediately after the title's heading, never inside it. Thus, the heading keeps the provider's name. The control sits above the row's overlay link. It stops the row's click, pointer, and key handlers, which would otherwise treat it as a row click.
Providers reuse rows for other work. Every sync, including a changed link address, leaves exactly one control per row for the work the row now names. It closes a preview whose work no row names anymore.
The worker registers content scripts at runtime, only for provider origins granted by the user.
When grants change, it registers them again. GitHub and GitLab are single-page apps.
The script rechecks the slot on WXT's wxt:locationchange and content changes.
If the provider replaced or moved it, the script remounts.
It clears the previous work's frame before the next one loads.
Removal and reinsertion of the same host also renew the mounting. Chrome recreates the iframe document, so old readiness cannot authorize a theme message to it. Every new document must complete the origin-checked readiness handshake.
Why not the web app's cookie
The web session is a __Host- cookie scoped to the instance origin. A read would need the cookies permission.
It would give the extension a credential with revocation, CSRF, and rotation rules designed for one browser tab family.
The extension signs in as an installed client
instead: its own session, listed and revocable like any other, with its own refresh lineage. It reads
no cookies and its API calls omit them. The sign-in itself runs in identity.launchWebAuthFlow, which uses the normal browser profile.
Thus, it can reuse an existing provider or Hephaestus sign-in.
An extension id is not an attestation. Ids and the public keys they derive from are public. The server's allowlist turns an id into one callback and one CORS origin, which restricts where a sign-in may return, not which package is running.
The controls are PKCE with no client secret, the exact
redirect, and the one-minute single-use handoff. Which packages users may run is managed distribution,
outside this design. The
sign-in handoff is ADR 0048.
The flow is in docs/auth-architecture.md.
Server surface
GET /workspaces/{slug}/practices/review-context?url=resolves the viewed page to reviewed work inside one workspace. It uses data already synced by the workspace and returns what the caller can do. Unknown, unmonitored, and inaccessible work return the same404.GET /workspaces/{slug}/practices/feedback/on-work?url=(getOwnDeliveredWorkFeedback) lists comments Hephaestus recorded posting on the work for the caller, regardless of role. It returns each piece of feedback's practices, delivery time, and recorded provider comments. It includes the provider comment handle and recorded permalink, but never a body. The worker counts one comment per handle and keeps handles private. It links only permalinks anchored on the work's own pages. The report shows this first.- Other reads reuse existing endpoints for the reader's own reviews and results.
They request the trace of the resolved item and the latest own review through
getOwnItemTrace. Results use the exactitemIdfilter when the report opens. The worker checks that each returned record concerns the resolved work, and refuses a list that does not. Admins use the same own-reader projection. A missing own run or refused trace is unavailable, not evidence that nobody reviewed the work. The extension reads no feedback body or other developer's records. These belong to the web app. - Review requests use the existing endpoint, only from the confirmation window. This is the extension's only change. Links lead to the reader's feedback page and, for workspace admins, reviewed-work output for run cancellation or retry.
Shared vocabulary
Labels and icons come from the webapp's registries under
webapp/src/components/practice-vocabulary/*-defs.ts, imported by path. @/api/* resolves to the
extension's own generated client, which is generated from the same spec, so the registries type-check
in both trees. There is no second copy of a label.
Develop locally
Two origins locally, one in production
In production, the web app and API share one origin.
The reverse proxy serves the API under /api and removes the prefix before the server sees it (hephaestus.auth.api-base-path).
The user enters that origin in the extension.
Locally they are two origins. The Spring server
listens on http://localhost:<SERVER_PORT> and serves the API at its root, with no /api prefix. The
web app runs on http://localhost:<WEBAPP_PORT>, which APPLICATION_HOST_URL names and the server
uses for every link back into the web app. Read both ports from server/.env.
The worker resolves what you enter on the options page (extension/src/background/instance.ts). It
first tries the API under <address>/api. If that answers, the address is a deployed instance and
also the web app's address. Only a development build, and only for a loopback address, then tries
the address itself as a bare API server.
That case needs the web app's address separately.
Every extension link goes to the web app, never the API server, where it would return a Spring 404.
These links include Open in Hephaestus, the consent step, review pages, and practice pages. So a
development build's setup form shows a second field, Web app address (local server only).
For a local stack, enter:
- Hephaestus address: the server origin,
http://localhost:<SERVER_PORT>. - Web app address:
http://localhost:<WEBAPP_PORT>, the same origin asAPPLICATION_HOST_URL.
These links require a running web app configured for the same server. Local development covers its ports and runtime configuration.
Run it
-
Start the local server as described in Local development. The
localprofile accepts the development extension id. For the isolated browser suite, use the server setup under Run the end-to-end suite locally. It also enables dev sign-in and does not need a review-runtime image. -
Run
vp run dev:extensionand leave it running. Loadextension/.output/chrome-mv3-devonce fromchrome://extensionswith Developer mode and Load unpacked, in a profile you keep for development. Start the browser manually (webExt.disabledin WXT). The extension connects to WXT's development server after it loads. Subsequent edits update automatically:- React and CSS edits update extension pages through Vite's hot module replacement.
- Provider script and style edits rebuild the script and refresh tabs on its currently registered sites.
- Background edits reload the extension. Its worker restores the report on allowed provider tabs.
While the development server is connected, you do not need repeated visits to Reload. WXT also supplies Alt+R for an explicit development reload. Use a dedicated profile because provider tab refreshes replace the current document, including any unsaved edits.
vp run --filter extension build:devmakes a static development bundle for testing with the server stopped. It has no hot-reload connection. After switching from that static build back todev:extension, reload the unpacked extension once to load WXT's development client. Keep the watcher running rather than overwriting its output withbuild:devduring development.Editing
wxt.config.tsor restarting the development server closes the worker's development connection. With this manually managed browser, reload the extension once after the server has rebuilt. Ordinary source edits then update automatically again. WXT's default browser runner handles configuration changes by restarting its own browser instead.A manual extension reload invalidates already-open extension frames. Refresh any provider tab that was left open under a static build. Chrome retains its earlier Errors entries across reloads. Record the details before clearing them when checking whether an error recurs. A stale frame's
Extension context invalidatedentry is not evidence that the refreshed page failed.To opt into automatic browser startup, follow WXT browser startup and use a dedicated development profile. Keep local startup settings ignored. No personal profile path belongs in the repository.
-
On the options page, enter both addresses as above and choose Connect. Development builds accept loopback HTTP. Production builds accept HTTPS only and never show the second field. Sign in with a configured provider or the dev client login (
GET /auth/dev-login/client). This login exists only while the server enables dev login.
A fresh clone needs no wxt prepare before vp run check: extension/tsconfig.json is
self-contained and auto-imports are off, so linting and type checking never read WXT's generated
.wxt/ directory.
WXT builds three modes from one source. production is what ships: HTTPS only, no manifest key, no
host permission until the user grants one. development adds the public key in
extension/dev-public-key.txt, which pins the id ijkajblcbajjpjbknfgdiiiljipafiko so the server's allowlist
and the Chrome identity redirect stay stable, and accepts loopback HTTP. e2e is development plus
pre-granted fixture origins, because Playwright cannot answer Chrome's permission prompt. Its
output is extension/.output/chrome-mv3-e2e.
Every contributor's local build shares the development id. Never allow it on a production instance.
The manifest version is the root
package.json release version in every mode. version_name marks development and e2e builds.
Runtime-script reloads
WXT owns the reload connection and browser lifecycle. Its pinned 0.21.4 release updates runtime registrations, but selects tabs with the entrypoint's declared matches.
This field is deliberately empty here.
The worker registers optional site access only after the user grants it. The package-manager
patch patches/wxt@0.21.4.patch corrects that framework function to use the actual registered
origins. It does not add host permissions, an application reload service or production code.
This also covers a background reload: WXT asks the new worker to refresh the registered content scripts when it reconnects. Unrelated tabs and the instance's own web app are not provider matches. When updating WXT, check its runtime reload implementation and remove the patch when the upstream implementation uses those registrations. Follow WXT's development guidance for browser startup. Do not add a second reload mechanism.
Commands
| Command | Does |
|---|---|
vp run dev:extension | WXT dev mode: load extension/.output/chrome-mv3-dev once. UI updates and provider/background reloads follow edits |
vp run check:extension / fix:extension | The tree's gates — type-aware lint and types, format, presentational components, story prose — or, with fix, safe fixes |
vp run gate:extension | Lint and type check, cached — part of vp run check |
vp run gate:extension-format | Format check, cached — part of vp run check |
vp run format:extension | Format the tree |
vp run test:extension | Vitest unit project |
vp run test:extension:stories | Storybook stories as Vitest browser tests, in Chromium |
vp run test:extension:e2e | Builds the e2e bundle (build:e2e), then runs Playwright against the real server. Needs the e2e profile running |
vp run build:extension | Production build and the zip the Chrome Web Store takes, extension/.output/hephaestus-<version>-production-chrome.zip |
vp run check:extension-package | Opens that zip and holds its manifest to the release: version, no key, the justified permissions, HTTPS-only optional hosts, default content security policy |
vp run ci:extension | What the Quality → Extension CI leg runs: gates, unit and story tests, then the production build and its package check |
vp run check:affected selects affected:extension for changes under extension/ or to the web app's entry stylesheet.
It also selects it for webapp files imported by path, including their imports.
These include the brand mark, vocabulary registries, status definitions, item kinds and slugs, and sign-in providers. A change to the
shared palette, webapp/src/styles/theme-tokens.css, selects the webapp, extension and docs scopes,
because all three depend on it. A change to the webapp lint plugin, extension/src/api or
server/openapi.yaml runs the whole gate.
Styles
The extension paints with the web app's palette and theme scale by importing
webapp/src/styles/theme-tokens.css, Tailwind's
documented way to share a theme. Its entry stylesheet, extension/src/ui/styles.css, adds base rules and a focus ring in mentor blue.
It also has one deliberate exception: the inline frame takes the provider page's appearance but cannot read its styles.
Thus, styles.css contains snapshots of GitHub's Primer and GitLab's Pajamas neutrals, links, focus rings, and radii.
These cover reviewed light, dark, dimmed, and high-contrast palettes, with the source releases named beside them.
extension/src/content/report.css reads
the provider's live design tokens for the report's box and keeps published values only as
fallbacks for a provider without them. Outcome and delivery colors always stay the shared
palette's. When a provider changes its palette, update the snapshot from the named release rather
than from its page. The extension renders no feedback text, so it bundles no Markdown renderer.
The inline report follows Pajamas' merge request report hierarchy and Primer's progressive disclosure: one attributed summary line — "Only level 1 can be expanded and collapsed". It opens into flat second-level rows for the reader's comments. Each row links to that comment on the provider. Then the reader's results, practice, outcome and one sentence each, and under a not met result the one-line summary of each problem. Then the precise review time and the links into the web app. Nothing inside opens further, and nothing repeats what the line already says.
A list row gets no report.
It gets one unboxed line (RowPreview), filled immediately, with the same sentence and links to the first comments. Each action has one visible placement. The report omits successfully empty groups, but still shows loading and failure states.
Keep Hephaestus visibly identified even when the report has a warning or error. Provider styling must not make a Hephaestus result look like a provider's merge check.
The report participates in the page's normal scrolling. Do not contain overscroll. Do not forward wheel events through the frame bridge. Native scroll chaining returns scrolling to the provider, including at the boundary of a report at the frame's height limit. Provider E2E tests exercise both boundaries.
Author extension code, configuration and test tooling in TypeScript or TSX. WXT emits the JavaScript Chrome executes. Generated bundles are not a reason to maintain a second handwritten JavaScript implementation. Provider visual differences belong in the scoped token and style adapter, not a second component tree or framework.
Tests
- Unit (
test:extension): provider URL grammar, RPC validation, sender checks, and the session state machine against a fakebrowserAPI. The state machine includes single-flight refresh and generation drops. Tests cover sign-in, context resolution, list-row placement and alignment, and provider origins registered by grants. Lint applies the web app's Vitest policy. Thus, a test narrows withsrc/testing/required.ts, not a branch. - Stories (
test:extension:stories): designed states of each surface, rendered from props without network access. They cover the report's identical height, the list row's single line, and flat opened reports. They also cover long lists, narrow windows, and provider themes. They cover the confirmation window's ready, sending, done, refused, unknown, and invalid states, plus settings. The accessibility check runs aterror. - Both write JUnit reports through Vitest's own reporter:
junit-unit.xmlandjunit-stories.xml, in the tree's test results. The Quality → Extension leg publishes them as its test report. It keeps the folder asextension-unit-reports-*for 14 days and records counts in the run's test metrics. - Production package (
check:extension-package): the browser suites never run the production build. The package check examines its manifest directly in the zip. The Quality → Extension leg keeps the zip as theextension-chrome-*artifact. - End to end (
test:extension:e2e): Playwright launches Chromium with thee2ebuild. Provider pages are fixtures Playwright serves in place of GitHub and GitLab. The Hephaestus server is real. The suite does not stub any server response. One project exercises the fixture pages alone. The other signs in through the dev client login and needs the server, and fails when it does not answer.
What the suites prove, and what they do not
They prove that the built extension puts one report in each fixture slot and follows navigation and tab switches.
It opens in the page's flow and keeps its content outside the page.
It opens the confirmation window without sending anything.
Against a real server with the e2e profile, it signs in, resolves seeded work to the correct workspace, and signs out. They do not prove:
- Provider ingestion. The pages are fixtures and the work is seeded. No webhook arrives, sync or review runs, or feedback reaches a provider.
- Real OAuth. The suite signs in through the dev client login. Verify production sign-in through GitHub or GitLab manually. Verify the Chrome sign-in window manually.
- The shipping permission prompts. The
e2ebuild pre-grants its fixture origins, so Chrome's optional-permission prompt is never shown. - The production zip in a browser.
check:extension-packagechecks that zip statically. The suite does not drive it in a browser. - The real provider pages. Slots are fixtures of shapes probed on real pages. A provider redesign shows up only on the real site.
- A fresh build in a reused browser profile. A persistent profile can keep an old service worker after the bundle changes. Prove a build in a fresh, disposable profile.
Report each of these separately from a green suite.
Run the end-to-end suite locally
The sequence CI's Build → Extension: E2E job runs, against your checkout instead of the packaged
JAR. Read SERVER_PORT and WEBAPP_PORT from server/.env. The defaults are 8080 and 4200.
-
Start local services, then the server with the same profile combination as CI:
vp run dev:compose:e2eHEPHAESTUS_INTEGRATION_GITLAB_ENABLED=true node scripts/run-gradlew.ts \:application:bootRun -Pprofiles=e2e,playwrighte2eenables dev sign-in and accepts the development extension id.playwrightdisables the worker, webhook receiver and NATS consumers: this suite exercises API behavior without pulling a review-runtime image or running review containers. The environment override keeps the GitLab adapter available for the seeded connection. Wait untilhttp://localhost:<SERVER_PORT>/readyzanswers. -
Create the
e2eadmin ande2e-devdeveloper accounts. Load the web app's seed, then the extension's seed. The extension's workspace belongs to the developer created by the web app's seed:curl --fail -X POST http://localhost:<SERVER_PORT>/auth/dev-login \-H 'content-type: application/json' -d '{"username":"e2e","admin":true}'curl --fail -X POST http://localhost:<SERVER_PORT>/auth/dev-login \-H 'content-type: application/json' -d '{"username":"e2e-dev","admin":false}'docker compose -f server/compose.yaml exec -T postgres \psql -v ON_ERROR_STOP=1 -U root -d hephaestus < webapp/e2e/seed.sqldocker compose -f server/compose.yaml exec -T postgres \psql -v ON_ERROR_STOP=1 -U root -d hephaestus < extension/e2e/seed.sql -
Install Chromium for Playwright once (
vp -C extension exec playwright install chromium). Build thee2ebundle. Run both projects with the server and web app addresses:E2E_SERVER_URL=http://127.0.0.1:<SERVER_PORT> E2E_WEB_APP_URL=http://localhost:<WEBAPP_PORT> \vp run test:extension:e2e
The extension's seed skips rows that already exist, so it can run again. vp run dev:reset starts
from an empty database.
Generated client
The extension's generate:api script generates extension/src/api/** from server/openapi.yaml. vp run generate:api:client regenerates it together with the webapp's client, and CI's
generated-artifact check and the autocommit-openapi label cover both. Commit it with the API change
that produced it. It is never formatted or linted, and never edited by hand.
Releasing to the Chrome Web Store
The extension is not yet published. Operators run a build they distribute themselves until it is. Publishing needs, once:
- A Chrome Web Store developer account for the publishing organization, with verified contact details. Enable 2-Step Verification on this account. Chrome requires it for publication and updates.
- A listing: name, description, icons, screenshots of the in-page report, the confirmation window and settings, and the support address.
- A privacy policy URL and completed privacy practices form.
State the single purpose: show the practice review a self-chosen Hephaestus instance recorded about the current tab's work.
Justify each permission (
storage,identity,scripting, and optional host permissions). Declare no remote code and the limited-use disclosure. After site permission, supported work addresses go automatically to the user's own instance. A list row's work address goes only when the user presses its Hephaestus button. The extension collects no page content, sells nothing, and uses nothing for another purpose.
Release is blocked until all these conditions hold:
- Public help and privacy pages linked by the extension are deployed and answer.
These are the
docs.hephaestus.builduser guide and extension privacy page. - Installed-client sign-in runs on
https://hephaestus.buildwith the exact store ID allowed and proven with the production package. - The listing text below matches that release.
Today those pages do not answer yet.
Then, per release:
- Build the zip with
vp run build:extension. Check it withvp run check:extension-package. Alternatively, takeextension-chrome-*from the release commit's Quality → Extension run, which did both. Its version is the release version. It carries nokeyfield. - For the first release, upload the zip as a draft in the developer dashboard. Publication is not needed to obtain the Item ID and public key. Follow Chrome's consistent extension ID instructions: Put that public key in the manifest of a separate unpacked test copy. Verify that its installed ID equals the draft Item ID. Keep the store zip unchanged. An unpacked production build without a key has an installation-dependent ID. Do not assume contributors share one.
- Allow the draft's ID on a review instance with
HEPHAESTUS_AUTH_BROWSER_EXTENSION_IDS. With the test copy, verify provider sign-in, session revocation, optional site grants, and contextual UI. Supply reviewers a working test account and clear instructions for reaching the extension's functionality. Publish the store ID in the admin guide when it is assigned, so operators can configure it before rollout. Before public rollout, deploy installed-client support. Allow that exact store ID onhttps://hephaestus.build. Verify the advertised default connection with the production package and real provider sign-in. - Complete the listing, privacy and distribution fields, and submit the unchanged zip for review. Review duration varies. Chrome supports deferred publication after approval. Follow the Chrome Web Store publication process. Subsequent releases update the existing item, retaining its ID.
The repository produces a store zip, not a signed .crx or a self-hosted update service. Managed
self-hosting needs its own signing and distribution process as described in the admin guide.
Listing and privacy fields
Keep the submitted text, in-product disclosure and published notice consistent with the same
release. The extension's single purpose is See Hephaestus practice reviews for the
GitHub and GitLab work you are viewing. The default service is https://hephaestus.build. Custom
instances are a secondary choice. A connected account, workspace access and an instance that
supports the store extension ID are required. Do not advertise GitHub Enterprise, Slack, Outline or
mobile support for this client.
Suggested listing description:
See the Hephaestus practice review of the pull request, merge request or issue you are reading, right in the page on GitHub or GitLab. One line says how many comments Hephaestus posted there for you and when the work was reviewed. Open it to jump to each comment and to see what the review concluded about your work. Everything else is one link away in Hephaestus. On a repository's list, press a row's Hephaestus button for a one-line preview without leaving the list.
Ask for a review from the page: a small Hephaestus window shows exactly what will happen, and nothing changes until you confirm there.
Connect to hephaestus.build or your organization's Hephaestus instance, sign in, and allow each site where Hephaestus may run. On an allowed site, the extension sends each opened pull request, merge request, or issue address to that instance immediately.
It uses the address to look up the review. It sends a list row's address only when you press its Hephaestus button. It does not upload the page's text, look up any other page, or collect a browsing history. Feedback meant for a developer's private practice page or for Hephaestus is never shown. A Hephaestus account and access to a connected workspace are required.
Use Chrome extension privacy as the extension-specific public disclosure and the default instance's Privacy notice for its controller, legal bases, recipients and rights contact. Publish and verify both before submission. A source file or a private preview is not a publicly accessible policy. Link the disclosure from the listing and the extension. A self-hosted operator must publish its own instance notice.
The dashboard's data-use declarations include authentication information and personally identifiable account information. They include supported work URLs under web history. They also include website content represented by review records received from the instance. Locally handled data also counts. Do not tick unrelated categories or claim no user data collected merely because credentials stay in memory. Review these fields against the actual release and Chrome's data-disclosure definitions.
Certify Limited Use only when the deployed data handling matches the published commitment.
| Permission | Justification |
|---|---|
identity | Complete sign-in through the selected instance and its identity providers. |
storage | Keep the chosen instance locally and credentials in session memory. |
scripting | Add the practice review report to supported pages on individually allowed sites. |
Optional https://*/* | Reach user-selected self-hosted Hephaestus and GitLab instances. Actual requests grant exact origins, never the wildcard. |
The extension uses no remote executable code. JavaScript and images ship in the package, fonts use the system stack, and server APIs return data. A requested practice review can invoke the server's configured AI provider, which the instance notice covers. Do not describe self-hosting as a guarantee of local AI processing.
Store artwork and reviewer walkthrough
Chrome's image requirements require these assets:
- A 128px PNG icon.
- A 440×280 small promotional tile.
- One to five screenshots at 1280×800 or 640×400. A 1400×560 marquee is optional. The brand exporter owns the icon and promotional tile. Screenshots must show the final working extension at a legible scale. Use non-sensitive test data and actual provider pages. Capture the in-page report (collapsed and opened), the confirmation window and the connection/site controls. Do not show unreleased features or use private review content as marketing material.
Supply the reviewer with a reachable test instance that allows the store ID. Supply an account with a connected demonstration workspace. Supply exact GitHub and GitLab work links. Include these steps in the dashboard's private test-instructions field, keeping credentials out of the repository:
- Connect, sign in, finish the app's account setup if needed, and allow each demonstration site.
- Open the supplied issue and pull/merge-request links, and their changes. The practice review sits after the description and above the changed files. Open it and compare its comments and results with the app. On a list, press a row's Hephaestus button and check the one line.
- Choose Request review on a disposable work item: the confirmation window names the work and workspace. Cancel once, then confirm once and check the outcome shown. Nothing in the page itself sends a change.
- Remove and restore site access while the page is open, then sign out in settings with the report open. Review content must disappear and must not return from an older pending response.
Run this walkthrough on the production package with the draft store ID, not just the fixture E2E build. Check hosted-default connection, self-hosted switching, OAuth cancellation, expired/revoked sessions, keyboard navigation, narrow provider layouts, light/dark modes and browser zoom. Record the tested commit, zip checksum, instance release and outcomes in the release evidence. Store submission and approval are separate from passing the repository gates.