Skip to content

did canvas text whatever and changed UI, but thils will probably be overwritten#315

Merged
xbubbo merged 2 commits intomainfrom
new-fun-canvas-stuff
Apr 21, 2026
Merged

did canvas text whatever and changed UI, but thils will probably be overwritten#315
xbubbo merged 2 commits intomainfrom
new-fun-canvas-stuff

Conversation

@juniorbutyeah
Copy link
Copy Markdown
Collaborator

did canvas text whatever and changed UI, but thils will probably be overwritten

Copilot AI review requested due to automatic review settings April 20, 2026 13:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new HTML text-obfuscation pipeline that replaces text nodes with encoded markers and renders text via canvas on the client, alongside some UI styling tweaks and missing TypeScript module declarations.

Changes:

  • Add transformTextInHtml() + a client-side canvas renderer script, wired into the HTML response transformation pipeline.
  • Extend obfuscation maps with a per-run textKey used for encoding/decoding.
  • Update UI typography/layout styles and add .d.ts shims for MercuryWorkshop modules.

Reviewed changes

Copilot reviewed 8 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/lib/text-canvas.ts New HTML transformer + injected client script that draws decoded text to canvas.
src/lib/obfuscate.ts Adds textKey to ObfuscationMaps and initializes it in generateMaps().
index.ts Injects the text-canvas client script and applies HTML text transform in the middleware.
package.json Adds node-html-parser dependency for server-side HTML parsing.
bun.lock Lockfile updates for the new dependency and override changes.
src/types/wisp.d.ts Adds a module declaration shim for @mercuryworkshop/wisp-js/server.
src/types/epoxy.d.ts Adds a module declaration shim for @mercuryworkshop/epoxy-transport.
src/pages/index.astro Adjusts landing page typography and search input sizing.
src/pages/settings.astro Minor typography tweak for the Settings heading.
src/layouts/Main.astro Updates nav layout/spacing/typography and menu sizing.
src/global.css Expands imported Inter font weights.
Comments suppressed due to low confidence (1)

src/lib/text-canvas.ts:287

  • The code attaches a MutationObserver to the entire document body (subtree + characterData + attributes) and also a ResizeObserver to many elements, triggering canvas re-renders. This is likely to be expensive on pages with lots of DOM updates/resize events. Consider narrowing the observation scope, debouncing/throttling redraws, and/or using requestAnimationFrame batching to avoid repeated synchronous reflows and canvas work.
              var par=n.parentNode;
              if(par&&par.nodeType===1){
                var tag=par.tagName&&par.tagName.toLowerCase();
                if(tag&&SKIP.indexOf(tag)===-1&&!par.hasAttribute("data-t")){
                  var text=n.nodeValue;
                  var sp=document.createElement("span");
                  sp.setAttribute("data-t-live","1");
                  par.replaceChild(sp,n);
                  draw(sp,text);
                  ro.observe(par);
                }
              }
            }
          }
        }else if(m.type==="characterData"){
          var tn=m.target;
          if(tn&&tn.nodeType===3){
            var par2=tn.parentNode;
            if(par2&&par2.nodeType===1){
              var tag2=par2.tagName&&par2.tagName.toLowerCase();
              if(tag2&&SKIP.indexOf(tag2)===-1&&!par2.hasAttribute("data-t")&&/\\S/.test(tn.nodeValue)){
                var text2=tn.nodeValue;
                var sp2=document.createElement("span");
                sp2.setAttribute("data-t-live","1");
                par2.replaceChild(sp2,tn);
                draw(sp2,text2);
                ro.observe(par2);
              }
            }
          }
        }else if(m.type==="attributes"){
          var t=m.target;
          if(m.attributeName==="data-t")renderSpan(t);
          else if(m.attributeName==="data-rph")renderPh(t);
          else if(m.attributeName==="value"&&t.hasAttribute&&t.hasAttribute("data-rph")&&t.__phtoggle)t.__phtoggle();
        }
      }
    });
    mo.observe(document.body,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:["data-t","data-rph","value"]});
  });
});
window.__dt=function(el,str){
  if(!el||el.nodeType!==1)return;
  var sp=document.createElement("span");
  sp.setAttribute("data-t-live","1");
  while(el.firstChild)el.removeChild(el.firstChild);
  el.appendChild(sp);
  draw(sp,str);
};
})();`;
  return `<script>${script}</script>`;
}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/text-canvas.ts
Comment on lines +107 to +113
doctype = m;
return "";
});
const root = parse(stripped, { comment: true });
walk(root as unknown as Node, key);
return doctype + root.toString();
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

aria-label and alt are cleared and only preserved in data-r-*, but the client script never restores these attributes. This will break accessibility (buttons/icons without labels) and image alternative text. Either do not clear these attributes, or add client-side decoding/restoration for data-r-aria-label and data-r-alt.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/text-canvas.ts
Comment on lines +158 to +162
ctx.clearRect(0,0,c.width,c.height);
ctx.font=f;
ctx.fillStyle=color;
ctx.textBaseline="alphabetic";
if(!ls){ctx.fillText(disp,0,baseline)}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The canvas-rendered text is forced to white-space: pre and rendered as a single inline-block canvas sized to the full measured string. This prevents natural line wrapping, so longer text (paragraphs, headings, menu items, etc.) will overflow instead of wrapping. If this feature needs to support general text, it should implement wrapping (split into words/lines) or avoid applying to multi-word/long text nodes.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/text-canvas.ts
Comment on lines +247 to +258
}else if(m.type==="characterData"){
var tn=m.target;
if(tn&&tn.nodeType===3){
var par2=tn.parentNode;
if(par2&&par2.nodeType===1){
var tag2=par2.tagName&&par2.tagName.toLowerCase();
if(tag2&&SKIP.indexOf(tag2)===-1&&!par2.hasAttribute("data-t")&&/\\S/.test(tn.nodeValue)){
var text2=tn.nodeValue;
var sp2=document.createElement("span");
sp2.setAttribute("data-t-live","1");
par2.replaceChild(sp2,tn);
draw(sp2,text2);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

Live text nodes are replaced with spans marked data-t-live, but the ResizeObserver callback only re-renders elements with data-t or descendants with [data-t]. data-t-live spans won’t be redrawn on resize/font changes, so they can become visually stale. Consider encoding live spans with data-t as well, or teaching the resize/font hooks how to re-render data-t-live nodes.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/text-canvas.ts
Comment on lines +71 to +77
if (tag === "meta") {
const name = (el.getAttribute("name") || "").toLowerCase();
const prop = (el.getAttribute("property") || "").toLowerCase();
const match = name === "description" || name === "twitter:description" || name === "twitter:title" || prop === "og:description" || prop === "og:title" || prop === "og:site_name";
if (match) {
const content = el.getAttribute("content") || "";
if (content && !isWhitespace(content)) {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The transform clears the <title> text (set_content("")) and only stores an encoded copy in data-rt, but the client script never decodes/restores data-rt. This will leave document titles blank for all pages. Either avoid modifying <title> at transform time, or extend the client script to decode data-rt and restore document.title on load and when it changes.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/text-canvas.ts
Comment on lines +80 to +97
}
}
return;
}

if (SKIP_TAGS.has(tag)) return;

const ph = el.getAttribute("placeholder");
if (ph && !isWhitespace(ph)) {
el.setAttribute("data-rph", encodeXor(ph, key));
el.removeAttribute("placeholder");
}
for (const k of ATTR_KEYS) {
const v = el.getAttribute(k);
if (v && !isWhitespace(v)) {
el.setAttribute(`data-r-${k}`, encodeXor(v, key));
el.setAttribute(k, "");
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

Meta tags (description / og:* / twitter:*) have their content attribute blanked and moved to data-rc, but the client script does not restore data-rc back into the content attribute. This will break SEO/snippets and social previews. Either keep meta content intact server-side or add client-side restoration for the affected meta tags.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/text-canvas.ts
Comment on lines +102 to +106
}

export function transformTextInHtml(html: string, key: number): string {
let doctype = "";
const stripped = html.replace(/^\s*<!DOCTYPE[^>]*>\s*/i, (m) => {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

This removes the native placeholder attribute and replaces it with a custom overlay, but the client script does not provide an equivalent accessibility story (e.g., restoring placeholder/aria-label or ensuring assistive tech can read it). Consider keeping placeholder intact and only visually obfuscating it, or explicitly restoring placeholder/adding an accessible label after decoding.

Copilot uses AI. Check for mistakes.
@xbubbo xbubbo self-assigned this Apr 21, 2026
@xbubbo xbubbo self-requested a review April 21, 2026 01:15
Copy link
Copy Markdown
Member

@xbubbo xbubbo left a comment

Choose a reason for hiding this comment

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

v

@xbubbo xbubbo merged commit 87f7cbb into main Apr 21, 2026
1 check passed
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.

3 participants