Skip to content

fix: follow ups and QoL improvements - #28

Draft
raisedadead wants to merge 15 commits into
mainfrom
fix/hint-formatting-followups
Draft

fix: follow ups and QoL improvements#28
raisedadead wants to merge 15 commits into
mainfrom
fix/hint-formatting-followups

Conversation

@raisedadead

@raisedadead raisedadead commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Follow-ups from the #27 review, plus one pre-existing bug and some CI wiring.

formatHintOutput re-parsed HTML in order to escape it. A parser discards what it doesn't model before escaping can preserve it as text, and applies content models that don't fit a plain-text hint. One cause, three defects:

input before after
Your <img src="cat.jpg"> is missing alt. Your &lt;img /&gt; … — attributes dropped Your &lt;img src="cat.jpg"&gt; …
Wrap it in <code><textarea></code> and try. <code>…&lt;/code&gt; and try.</code> — swallowed closing tag <code>&lt;textarea&gt;</code> and try.
Comments look like <!-- this -->. Comments look like . — comment deleted Comments look like &lt;!-- this --&gt;.

Tags are now found in the raw output and everything between them is escaped. Detecting tags before escaping is what stops the renderer mistaking model-written &lt;code&gt; for markup it produced itself. Drops the sanitize-html dependency.

Fixes

  • Prompt-frame strip loops until stable — one pass let <stu<student_code>dent_code> reassemble into <student_code>.
  • challengeType guard restored — unrecognised values map to undefined again, as before Refactor/stabilize hint contract #27. The return type and docstring both already claimed this. No HTTP-visible change.
  • Response limits declaredMAX_HINT_RESPONSE_CHARS is derived (≤5 chars per escaped code point), matching the measured worst case. Documents element-context-only insertion, since "/' aren't escaped. Fastify doesn't enforce response maxLength; this is for API consumers.
  • test:manual no longer fails on a healthy dev server — its 401/403 cases can't pass outside production/staging yet gated the exit code, and spent two Groq calls doing it. The runner now detects whether auth is enforced and skips them when it isn't.
  • Sentry flush on startup failure (src/index.ts) — pre-existing, not from Refactor/stabilize hint contract #27. Boot failures were logged at error then dropped before the transport drained.

CI and tooling

  • pnpm run verify (check && typecheck && test) is now the single source of truth; the CI workflow calls it. Local and CI can no longer drift — this PR's first CI failure was exactly that drift, pnpm lint locally vs pnpm run check in CI, so prettier never ran.
  • typecheck now covers scripts/ via tsconfig.check.json (noEmit); tsconfig.json stays the build config and dist/ is unchanged. src/**/__tests__ stays excluded — vitest's axios mocking and dynamic import() don't satisfy moduleResolution: nodenext, so including them means rewriting mocks, not fixing bugs.
  • oxlint ignores .scratchpad/dist/coverage, matching .prettierignore. Local file count now matches CI's.

CLAUDE.md updated throughout: #27 moved auth to onRequest, inverting the documented hook order.

Verification

pnpm run verify — 104 tests (up from 88), lint and prettier clean, typecheck clean; pnpm run build still emits the Lua script. Every behaviour change written test-first. Verified live against a dev server: contract runner exits 0; four /hint calls across html/css/javascript returned balanced <code> and no active non-code markup.

The chore:/ci: commits are independent of the fix: ones and can be cherry-picked out if you'd rather keep this PR purely about the hint formatting.

@raisedadead
raisedadead marked this pull request as draft July 31, 2026 16:47
@raisedadead raisedadead changed the title fix: escape-based hint formatting fix: follow ups and QoL improvements Jul 31, 2026
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.

1 participant