Skip to content

feat: append revision footer to app pages#26

Closed
LYL-8bit wants to merge 1 commit into
ubiquity:mainfrom
LYL-8bit:add-revision-footer
Closed

feat: append revision footer to app pages#26
LYL-8bit wants to merge 1 commit into
ubiquity:mainfrom
LYL-8bit:add-revision-footer

Conversation

@LYL-8bit

Copy link
Copy Markdown

Summary

  • Appends a small fixed-position router revision footer to successful proxied HTML app responses
  • Links the displayed revision to the ubiquity/ubq.fi-router commit when a deployed SHA is available, with a local fallback for development
  • Leaves non-HTML responses unchanged and removes stale content-length after HTML injection
  • Adds routing tests for HTML footer injection and non-HTML passthrough

Validation

  • Static diff reviewed
  • Could not run bun test locally because bun is not installed in this runner and npm dependency install timed out; CI should run the repository's Bun test workflow

Resolves #6

@ubiquity-os

ubiquity-os Bot commented May 24, 2026

Copy link
Copy Markdown

Warning

@LYL-8bit this pull request is linked to an issue that is already assigned to another user. Please link it to an open issue assigned to you or to an unassigned open issue.

@ubiquity-os ubiquity-os Bot closed this May 24, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36d74f1008

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/worker.ts
const contentType = response.headers.get('content-type')?.toLowerCase() ?? ''
if (!contentType.includes('text/html')) return response

const html = await response.text()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve upstream charset when rewriting HTML

Calling response.text() here decodes the upstream bytes as UTF-8, and the rewritten response then re-encodes that string while keeping the original Content-Type header. For any HTML page served with a non-UTF-8 charset (for example text/html; charset=iso-8859-1), this will corrupt characters and can produce a body/header charset mismatch that did not exist before this commit.

Useful? React with 👍 / 👎.

Comment thread src/worker.ts
Comment on lines +261 to +263
const headers = new Headers(response.headers)
headers.delete('content-length')
return new Response(updatedHtml, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop cache validators after mutating HTML responses

The response body is modified by footer injection, but only content-length is removed; validators like ETag/Last-Modified are forwarded unchanged. That allows conditional requests to be validated against the unmodified upstream representation, so clients can receive 304 Not Modified and keep a cached page with an outdated injected footer across router deployments.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds dynamic revision footer injection to HTML responses. A new ROUTER_REPO_URL constant and renderRevisionFooter function render footer markup with revision hash and repository link. The withRevisionFooter middleware conditionally injects the footer into 2xx text/html responses for non-HEAD requests by reading the response body and inserting the footer before the closing </body> tag (or appending if not found), then removing the content-length header. The response handler integrates this middleware before the existing withRouterRevision header step. Tests verify footer injection on HTML responses, absence of injection on non-HTML responses, and correct header behavior.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: appending a revision footer to app pages.
Description check ✅ Passed The description clearly relates to the changeset, detailing the footer implementation, revision linking, and test coverage.
Linked Issues check ✅ Passed The PR directly addresses issue #6 by injecting a revision footer with links, matching the stated objectives.
Out of Scope Changes check ✅ Passed All changes are scoped to footer injection and related test coverage; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/worker-routing.test.ts (1)

90-102: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same TypeScript error here.

Apply the same signature fix as above.

Proposed fix
-  test('does not append the router revision footer to non-HTML responses', async () => {
-    globalThis.fetch = (async () => new Response('{"ok":true}', {
+  test('does not append the router revision footer to non-HTML responses', async () => {
+    globalThis.fetch = (async (_input: RequestInfo | URL, _init?: RequestInit) => new Response('{"ok":true}', {
       headers: { 'content-type': 'application/json' },
     })) as typeof fetch

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 292b5693-d88b-4a0b-ac67-5a308dcd2399

📥 Commits

Reviewing files that changed from the base of the PR and between 91bebfb and 36d74f1.

📒 Files selected for processing (2)
  • src/worker.ts
  • tests/worker-routing.test.ts

Comment on lines +70 to +88
test('appends the router revision footer to successful HTML app responses', async () => {
globalThis.fetch = (async () => new Response('<html><body><main>app</main></body></html>', {
headers: {
'content-length': '42',
'content-type': 'text/html; charset=utf-8',
},
})) as typeof fetch

const res = await worker.fetch(new Request('https://pay.ubq.fi/'), {} as Env)
const html = await res.text()

expect(res.status).toBe(200)
expect(res.headers.get('x-uos-router-revision')).toBe('local')
expect(res.headers.get('content-length')).toBe(null)
expect(html).toContain('<main>app</main>')
expect(html).toContain('id="uos-router-revision"')
expect(html).toContain('https://github.com/ubiquity/ubq.fi-router')
expect(html).toContain('>local</a>')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix TypeScript error: mock signature mismatch.

CI fails because the arrow function lacks parameters that typeof fetch expects. Match the existing test pattern.

Proposed fix
-  test('appends the router revision footer to successful HTML app responses', async () => {
-    globalThis.fetch = (async () => new Response('<html><body><main>app</main></body></html>', {
+  test('appends the router revision footer to successful HTML app responses', async () => {
+    globalThis.fetch = (async (_input: RequestInfo | URL, _init?: RequestInit) => new Response('<html><body><main>app</main></body></html>', {
       headers: {
         'content-length': '42',
         'content-type': 'text/html; charset=utf-8',
       },
     })) as typeof fetch
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('appends the router revision footer to successful HTML app responses', async () => {
globalThis.fetch = (async () => new Response('<html><body><main>app</main></body></html>', {
headers: {
'content-length': '42',
'content-type': 'text/html; charset=utf-8',
},
})) as typeof fetch
const res = await worker.fetch(new Request('https://pay.ubq.fi/'), {} as Env)
const html = await res.text()
expect(res.status).toBe(200)
expect(res.headers.get('x-uos-router-revision')).toBe('local')
expect(res.headers.get('content-length')).toBe(null)
expect(html).toContain('<main>app</main>')
expect(html).toContain('id="uos-router-revision"')
expect(html).toContain('https://github.com/ubiquity/ubq.fi-router')
expect(html).toContain('>local</a>')
})
test('appends the router revision footer to successful HTML app responses', async () => {
globalThis.fetch = (async (_input: RequestInfo | URL, _init?: RequestInit) => new Response('<html><body><main>app</main></body></html>', {
headers: {
'content-length': '42',
'content-type': 'text/html; charset=utf-8',
},
})) as typeof fetch
const res = await worker.fetch(new Request('https://pay.ubq.fi/'), {} as Env)
const html = await res.text()
expect(res.status).toBe(200)
expect(res.headers.get('x-uos-router-revision')).toBe('local')
expect(res.headers.get('content-length')).toBe(null)
expect(html).toContain('<main>app</main>')
expect(html).toContain('id="uos-router-revision"')
expect(html).toContain('https://github.com/ubiquity/ubq.fi-router')
expect(html).toContain('>local</a>')
})
🧰 Tools
🪛 GitHub Actions: CI / 2_test.txt

[error] 71-71: tsc type-check failed (TS2352): Conversion of type '() => Promise' to type 'typeof fetch' may be a mistake. Property 'preconnect' is missing in type '() => Promise' but required in type 'typeof fetch'.

🪛 GitHub Actions: CI / test

[error] 71-71: TypeScript (tsc --noEmit) TS2352: Conversion of type '() => Promise' to type 'typeof fetch' may be a mistake. Property 'preconnect' is missing in type '() => Promise' but required in type 'typeof fetch'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamically append the revision hash on every app footer

1 participant