Project pages assume they are served at the site root, which breaks under a coilyco.ai vanity proxy #138
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
burndown-2026-06
burndown-2026-08
icebox
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilysiren/website#138
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed by tpm 2026-08-28 at Kai's request, out of a session on
coilyco.aivanity domains.Context
Kai is putting project pages behind
coilyco.aisubdomains, starting withumbra.coilyco.aiproxied to/projects/umbra. The mechanism is a Netlify rewrite rather than a redirect,status = 200, so the vanity hostname stays in the address bar. The name in the address bar is the entire point, so anything that navigates a visitor offumbra.coilyco.aidefeats the feature.DNS facts as of filing:
coilysiren.meresolves to75.2.60.5, which is Netlify.coilyco.aihas Route 53 nameservers and nothing at the apex.The problem, which is broader than it first looked
I originally flagged only absolute self-links, where an
https://coilysiren.me/...href bounces the visitor off the vanity host on their first click. That is real but it is the smaller half.The bigger issue is that the page's whole navigation assumes it is served at the site root. Read live,
/projects/umbralinks to://about//hiring//resume//projects/umbra/docs//projects/mcp-beaver//projects/agent-compose//images/marks/umbra.pngUnder the proxy every one of those resolves against
umbra.coilyco.ai, notcoilysiren.me. What happens next depends entirely on how the rewrite is written, and both obvious spellings are wrong.from = "https://umbra.coilyco.ai/*"to a fixedto = ".../projects/umbra"withforce = trueswallows every path, so/about/silently serves the umbra page. That is worse than a 404 because nothing looks broken.forceleaves those paths to the normal site routing on a host that has no such pages, so they 404.What I verified and what I did not
Verified, by fetching the live page: the link and asset targets listed above are what the page points at.
Not verified: whether those hrefs are written absolute or root-relative in source. The fetch normalised them, so it cannot distinguish the two, and I am not guessing. That is the first thing to check in the repo, because it decides whether this is one problem or two.
What to do
coilysiren.me. Any absolute self-link is a straight bug for this feature and should become root-relative regardless of what else is decided.umbra.coilyco.aiclicking Resume should probably land oncoilysiren.me/resume/, which means those particular links want to be absolute on purpose. That is the opposite of step 1 for those links specifically, so the two need reconciling rather than a blanket rule./projects/umbra/docs/and the mark image, since a broken image is the most visible failure and the docs subpath is the one real nested route.canonicalon the proxied page pointing at thecoilysiren.meURL, so two hostnames serving identical content does not become a search problem.Priority
priority/P3. Nothing depends on it yet, becauseumbra.coilyco.aidoes not exist. It is a prerequisite for that subdomain being worth pointing anyone at, rather than a defect in the site as it stands today. The site is fine right now. This only bites the moment the proxy goes up.Not in scope
The
coilyco.aiapex. It has no destination to proxy to and a landing page there is a separate piece of work, deliberately not folded in here.Two corrections from measuring the live site
Both affect how this gets implemented, so recording them rather than leaving them in a session.
www is canonical, not the apex
Measured 2026-08-28:
https://coilysiren.me/returns 301 withlocation: https://www.coilysiren.me/https://www.coilysiren.me/returns 200www.coilysiren.meis a CNAME atcoilysiren-dot-me.netlify.app, apex points at75.2.60.5So
www.coilysiren.meis the Netlify primary domain and the bare apex is an alias redirected to it.Step 5 in the body is wrong on this point. The canonical tag should point at the
www.coilysiren.meURL, not the barecoilysiren.meone. A canonical pointing at a hostname that 301s is a self-inflicted extra hop.The automatic primary-domain redirect is live, which raises the stakes on this issue
Netlify redirects non-primary domains to the primary by default, and the apex 301 above is that behaviour running in production on this site today.
That is the first thing to verify when
umbra.coilyco.aiis wired up, before any of the link work here is worth doing. If the alias gets 301'd towww.coilysiren.mebefore the rewrite rule runs, the vanity hostname is gone from the address bar and this entire issue is moot until that is solved.force = trueis what is supposed to take precedence. Treat that as unverified. The check is loadinghttps://umbra.coilyco.ai/and watching whether the address bar holds.Rewrite target should be a path, not a URL
For a
status = 200rewrite, use a site-relative target so the host question does not arise:A full-URL target of
https://coilysiren.me/projects/umbrawould take the apex-to-www 301 on every request.Measured the open question. Steps 1 and 5 are already satisfied, so this is one problem rather than two
The body names the absolute-versus-root-relative question as "the first thing to check in the repo" and leaves it explicitly unverified. Answered from the served HTML rather than the source, which is the artifact the rewrite actually acts on.
Every internal link and asset is root-relative
https://www.coilysiren.me/projects/umbra/, raw attributes as served:The only absolute references to
www.coilysiren.meon the page are five, and every one of them is supposed to be absolute:So step 1 has nothing to fix. There is no absolute self-link in the navigation. The "straight bug for this feature" the body anticipated does not exist, and the smaller half of the problem is closed.
Step 5 is already done, and already correct
The canonical exists and already points at
www, which is what the correction in the comment above asks for. It is not project-page-only. Measured across the site:Site-wide, absolute, and on the primary host. The duplicate-content risk this issue raises is already mitigated by construction, on every page a vanity host could serve.
Which reconciles this with #133
#133 decided against subdomains on the grounds that they split authority, and this issue proposes subdomains. Those only conflict if the vanity host is indexable as its own site. It is not: a
status = 200rewrite serves pages that already carry an absolute canonical pointing back atwww.coilysiren.me. Search engines consolidate on the canonical, so authority still lands in one pool and #133's argument survives the mechanism intact.Worth stating in one of the two issues, because a future reader finding both will otherwise think a decision was reversed.
What is actually left
One problem, the one the body correctly called the bigger half: root-relative links resolve against the vanity host. Since the nav is entirely root-relative and the canonical is already right, the safe shape is the second option in step 2, serving the whole site under the vanity host rather than a single page:
Nav then works, assets resolve,
/projects/umbra/docs/works, and every page still declares its canonical onwww. The cost is thatumbra.coilyco.ai/resume/exists and serves the resume, which is odd but harmless and already canonicalised. Mapping the bare host to/projects/umbraand letting everything else splat is the variant worth trying first if that oddness matters.Still unverified and still the gating question, unchanged from the comment above: whether Netlify's automatic primary-domain redirect fires before
force = true. Nothing here can be tested untilumbra.coilyco.aiexists.Live DNS, since the body's facts are a day old
coilyco.aistill has Route 53 nameservers and no records at all: apex,www, andumbraall resolve to nothing.coilysiren.mestill 301s towww, which serves 200. So nothing has moved and this remains prospective.Decided: swapped chrome via build variants. Supersedes my allowlist recommendation above
Kai's call, 2026-08-28. The vanity host gets its own header and footer rather than inheriting the site nav. That changes the shape of this issue enough that my previous comment's
to = "/:splat"rule should not be built.A
status = 200rewrite cannot do this on its own. It serves identical bytes and the origin never learns the Host, so swapped chrome has to exist as built output or be assembled at the edge. Build variants win here:base.njkalready includes nav and footer at two lines, so this is a flag and a second include rather than a runtime dependency.The swap dissolves most of this issue
Two findings from reading the repo that shrink the problem:
footer.njkcontains no links. It is a name block. Swapping it is cosmetic and nothing routes through it.nav.njkcarries the brandhref="/", andlinks.njkcarries/about/,/hiring/,/resume/plus a GitHub link that is already absolute.So the "visitor bounces off the vanity host" problem was never routing. It was the shared nav. Vanity chrome that does not link to those paths removes the need for a path allowlist or denylist entirely, and the brand-link conflict I raised earlier (
/is both the vanity root and Home) disappears, because vanity chrome can link Home absolutely towww.coilysiren.me.What changes
Templates. A
vanityflag on the page,base.njkselectingnav-vanity.njkandfooter-vanity.njkwhen set. The vanity nav links outward absolutely; the vanity footer carries the "part of coilysiren.me" line.Docs are cheap, contrary to what I assumed.
eleventy.config.jsgenerates docs pages in a loop overdocsMountListrather than as hand-written files, so the vanity set is a loop parameter, not 20 new templates.Internal links must be relative to the vanity root, and this is the load-bearing detail. Vanity pages emit at
/vanity/umbra/and/vanity/umbra/docs/<page>/, and their own internal links are written as/and/docs/<page>/, not/projects/umbra/.... That is what lets a single splat rule work:An empty splat resolves to
/vanity/umbra/, so the bare host needs no separate rule.Assets stay shared rather than being emitted absolutely, deliberately. Absolute asset URLs would put fonts cross-origin and depend on CORS headers this site does not currently promise.
/fonts/*is required and is invisible in the HTML. It appears only insidesite.cssasurl("/fonts/roboto-latin-400-normal.woff2"). An asset list built fromhrefandsrcalone ships a vanity host with no webfonts and no obvious cause.Must not break
umbra.njkalready carriescanonical: /projects/umbra/in front matter. Vanity variants keep pointing there, which is what preserves #133's single-authority argument through this mechanism.robots. Vanity pages should benoindexor rely on the canonical, chosen deliberately rather than inherited.Sequencing, which matters more than the build
Prove the vanity host holds the address bar before building any of this. The gating unknown from the earlier comment is now worse than untested: Netlify documents
force = trueas overriding file shadowing, and says nothing about it beating the automatic primary-domain redirect. The flag this design leans on is not documented to do the thing it is being relied on for.coilyco.aicurrently has Route 53 nameservers and no records at all, so the cheapest proof is a single record plus one throwaway rewrite, watching whether the address bar survives. If it does not, the whole variant build is wasted work.Corrections to my earlier comment
to = "/:splat"as a single rule is wrong regardless of this decision: the bare host splats to/and serves the site homepage, not the umbra page.Live and measured.
force = trueholds, and the worry this issue was built around never appliedAll three vanity hosts are serving. Shipped in
8bdc099, probed against production.Zero redirects. The hostname stays in the address bar, which was the entire point and the thing this issue could not assume.
The gating fear was real and misaimed
Two comments up I raised that Netlify redirects non-primary domains to the primary, that the apex-to-
www301 is that behaviour live on this site, and thatforce = trueis documented only as overriding file shadowing rather than that redirect. That was right about the documentation and wrong about the risk.Vera measured the alias with no rewrite rule in place at all and it returned 200 on its own hostname with zero redirects, where the apex under the same command took its usual 301 to
www. A domain alias and the apex are not the same mechanism. The default I spent three comments designing around does not apply to aliases.So the probe ended up testing the rewrite rather than testing whether the hostname survived, which is a much smaller question, and the answer is that it works.
What is live, end to end
Chrome swaps. Each host wears its own project, not the site:
Canonical is intact, so the hosts do not split authority and #133's decision stands:
<link rel="canonical" href="https://www.coilysiren.me/projects/umbra/">.Assets pass through, including the one that would have failed silently:
Everything else leaves for the real site rather than 404ing on a host with no such page:
That last row is worth naming: an unknown path leaves and then 404s on the real site rather than here. Deliberate, and the alternative is a 404 on a hostname with no navigation to recover from.
www.coilysiren.me/projects/umbra/still serves 200 with the site's own nav, unchanged.What this issue can close on
Steps 1 and 5 were already satisfied before any work started, measured earlier. Steps 2, 3 and 4 are answered by the swapped chrome: the vanity host simply never offers the links that would have needed an allowlist, so the scoping and nav-behaviour questions dissolved rather than being decided.
The only thing left is the apex, which the body scopes out.