Overhaul the template: KERNEL branding, current SDKs, streamed steps sidebar - #6
Open
dprevoznik wants to merge 32 commits into
Open
Overhaul the template: KERNEL branding, current SDKs, streamed steps sidebar#6dprevoznik wants to merge 32 commits into
dprevoznik wants to merge 32 commits into
Conversation
…sidebar - restyle to the KERNEL design system: kernel green, beige, and charcoal tokens, Inter + IBM Plex Mono, square corners, lowercase copy, and the brand-assets wordmark and mark - upgrade ai to 7, @ai-sdk/openai to 4, @ai-sdk/react to 4, @onkernel/sdk to 0.100, next to 16.3, react to 19.3 - inline the playwright execution tool so the agent works on the current AI SDK instead of the ai-5-pinned package - stream agent steps into a sidebar next to the live view, with the generated playwright, per-step status, execution time, return values, and failures - fix the eslint setup for eslint 9 and drop unused deps and assets
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
next 16 blocks its own dev resources from hosts that are not in allowedDevOrigins, which leaves the page unhydrated when the dev server is opened at 127.0.0.1 instead of localhost.
- pass req.signal into createAgentUIStreamResponse so stopping a run ends the agent loop instead of only the client stream - render an unfinished step as cancelled once the stream is gone, rather than leaving it spinning - check the delete response before clearing the session, so a failed delete surfaces instead of dropping the session id while the browser keeps running
a stopped run leaves a tool call without a result, so the next request failed with a missing tool result error. convert the message history with ignoreIncompleteToolCalls so the model only sees finished calls, and derive the cancelled state from the run itself so an interrupted step does not turn back into a running one when a follow-up task starts.
- create browser sessions at 1024x768, so the live view is legible in the space the page gives it instead of being a scaled-down 1920x1080 - move the three-call strip into the browser column as a list, where it fits, rather than a three-up grid squeezed into a third of it - shrink the step numbers to match the labels beside them, and refresh the preview screenshot
- 1280x800 keeps the live view readable without the 1920x1080 default, and the frame goes to 16:10 to match - "browser vm session" replaces "chromium session" in the hero, and the create card says the same
with a session open the page no longer scrolls: the live view takes the height that is left, the steps pane owns the only scrollbar, and the footer is only rendered on the landing page. narrower than lg still flows normally.
- the session error renders above the split again, instead of becoming a third column in the flex row - the live view keeps its 16:10 box below lg, where the column has no fixed height, and sizes itself from the available height at lg
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1029684. Configure here.
the live view is a 16:10 box that grows to 900px wide and then stops, centered in its column and sized from the space around it, so it neither elongates on a tall screen nor letterboxes on a short one. the split itself is capped at 820px tall and centered in the viewport, and the call list lines up with the live view. below lg everything still sizes down and scrolls as before.
playwright gives every locator action a 30 second budget by default, which is
longer than anyone watches. the instructions now require a short one: open a
snippet that touches a selector with page.setDefaultTimeout(5000), or pass
{ timeout: 5000 } to the action, with up to 15000 allowed for a navigation.
they also point out that checking count() first beats awaiting a value that may
not exist, and that a .catch() does not shorten the wait it wraps. the tool
description repeats the rule for the model and in the code parameter's help.
kernel's timeout_seconds only counts inactivity, and a live view counts as activity, so a tab left open holds a browser forever. sessions now carry a tag and a deadline: the page closes the session when the clock runs out (a short interval, since browsers clamp long timeouts), and every create sweeps this app's sessions that are already past it, so an abandoned tab cannot leak one. the session bar shows the remaining minutes, and SESSION_TTL_MS in lib/session.ts is the only number to change.
this was not the behaviour we wanted: sessions keep the inactivity timeout and the explicit close, and no longer carry a deadline, a tag, or the sweep.
req.json() threw on an empty or malformed body before either route's own validation ran, so Next returned an opaque 500 instead of the intended 400 for a missing sessionId.
KERNEL_API_KEY was checked but OPENAI_API_KEY, the other variable .env.example asks for, was not - a deploy with only the former set first fails deep inside the agent stream. toUIMessageStreamResponse also defaulted onError to a fixed "An error occurred." string, so that failure (and any other) reached the sidebar with no detail. This is a template running on the deployer's own keys, so showing the real message is the right tradeoff.
timeout_seconds matched maxDuration exactly, so a task started any time after the browser was created could hit a session that expires mid-run - surfacing as a raw SDK error on a playwright_execute step instead of a normal timeout.
A missing server env var is a server misconfiguration, not a bad client request. The response body already said as much; the status code now agrees with it.
The 400-char clone url was hand-typed in the route and in README with no link between the two copies. It now lives in lib/deploy-url.ts and the route imports it; README still needs its own literal for the badge markdown, so a comment there points back at the shared constant.
The README badge can't import the shared constant, so leave a pointer back to it for whoever updates one copy and forgets the other.
response.json() rejects on an HTML error page (e.g. a route that throws before returning JSON), which surfaced as an opaque parse error instead of an actionable message.
session lived only in component state, so reloading the page stranded a live browser the user could no longer see or close - it just burned until its session timeout. It's now mirrored to sessionStorage and restored on mount. Step history isn't restored (useChat owns that separately), and this deliberately doesn't try to close the session on beforeunload: that would fire on refresh too and delete the very session this is meant to recover, and the bounded session timeout already caps an abandoned tab's cost.
"1 steps" read as an oversight, and the composer hint said "cmd + enter" even though the handler already accepts ctrlKey too.
Steps and notes were tracked in two separate arrays and rendered steps-then-notes, so a note the model said before step 2 would render below step 5. They're now one ordered list of entries per run, so the rendered order matches the stream order. Also preserves case on notes: the model's closing prose was being force-lowercased along with static UI copy, the same inconsistency the task text above it (which does carry data-preserve-case) made visible.
When the stream ended in a server error, busy went false and every run's unfinished step got the same "cancelled" chip as an actual user stop - misleading, since the user didn't cancel anything. Only the newest run's own chat status can tell the two apart, so it now flows through to pick "failed" instead.
stepCountIs(24) ended a run silently: no note, no final prose, and the sidebar showed a finished-looking run with no indication it was cut off. The limit is now a shared constant, the model is told its budget in the instructions, and the sidebar flags a run that used all of it.
stderr came back from playwright.execute and went to the model, but the sidebar dropped it - the most useful thing on screen for a failed step. stdout stays unrendered and untracked in the sidebar's own Step type; the SDK response itself is unaffected.
Neither getHighlighter().then(...) nor copy() had a rejection path - a lazy shiki chunk-load failure logged an unhandled rejection, and a clipboard write that fails (denied permission, non-HTTPS origin) left the copy button silently doing nothing.
highlighter ??= kept the failed promise forever, so one transient chunk-load failure permanently disabled syntax highlighting for the rest of the page's life. It now clears the cache on rejection so the next call retries.
useEffect(..., [code]) re-ran shiki on every token appended during input-streaming, costing a full highlight pass per chunk on top of the dangerouslySetInnerHTML swap. Highlighting is now skipped until the step leaves the "writing" state and the code is final; the plain <pre> fallback already covers the streaming case.
The scale lived in @layer components as plain classes, and Tailwind only generates variants (md:, hover:, etc.) for utilities declared with @Utility - so md:text-heading-02 on the hero <h1> was dead CSS and the headline never grew past text-heading-04 at any breakpoint. Verified in the built stylesheet: md\:text-heading-02 now emits a rule.
/api/agent takes any sessionId and runs model-written playwright against it with no auth or rate limiting - worth a line under the deploy button given "Deploy with Vercel" makes a public deploy the default path. Also corrected the streaming section, which still named createAgentUIStreamResponse after the route moved to toUIMessageStreamResponse, and added the two new lib/ files to the code map.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Brings the template onto the current KERNEL brand and current SDKs, and replaces the end-of-run steps modal with a sidebar that streams while the agent works.
What changed
Branding. Replaced the black/purple styling with the KERNEL design system: kernel green, beige, and charcoal tokens (the palette is closed — default Tailwind colors are cleared in
globals.css), Inter + IBM Plex Mono, square corners, lowercase copy, and the wordmark and mark from kernel.sh/brand-assets.Dependencies.
ai@ai-sdk/openai@ai-sdk/react@onkernel/sdknextreact/react-domtailwindcssgeist,@onkernel/ai-sdk,playwright-coreshiki@onkernel/ai-sdkpinsai: ^5and has not been published since november 2025, so it blocks the AI SDK upgrade. Its whole implementation is a 25-line wrapper aroundkernel.browsers.playwright.execute, so the tool now lives inlib/playwright-tool.tswith the same name, input schema, and behavior. That also makes the execution path readable in the template itself, which is the point of the sidebar.Streaming.
/api/agentmoves from a blockingagent.generate()to aToolLoopAgentwithcreateAgentUIStreamResponse, and the client renders thetool-playwright_executeparts fromuseChat. Steps, generated code, timing, return values, and failures appear while the run is in flight instead of after it ends, and the composer stays usable for follow-up tasks on the same session. Stopping a run aborts the agent loop (not just the client stream) and marks the unfinished step as cancelled.Layout. The browser live view and the steps sit side by side, with the session id, spin-up time, region, and stealth state in the panel header, and a strip underneath showing the three SDK calls with that session's numbers.
Housekeeping. The lint script pointed at
next lint, which no longer exists in Next 16, and.eslintrc.jsondoes not work with ESLint 9; both are replaced with a flat config and all findings are fixed.typescript.ignoreBuildErrorsis gone so the template typechecks on build.allowedDevOriginslets the dev overlay load when the server is opened at127.0.0.1rather thanlocalhost. Unused components, assets, andconsole.logs are removed.Verification
bun run typecheck,bun run lint, andbun run buildpass.Notes
@onkernel/ai-sdk; if that stays the documented path, itsaipeer range should be widened to^5 || ^6 || ^7.Note
Medium Risk
Large dependency and agent-path refactor plus unauthenticated
/api/agentthat runs model-generated Playwright on any suppliedsessionIdusing deployer API keys.Overview
Rebrands the Next.js template to KERNEL (beige/green tokens, Inter + Plex Mono, new hero and split layout) and upgrades the stack to AI SDK 7, gpt-5.4, and a current @onkernel/sdk, dropping
@onkernel/ai-sdkin favor oflib/playwright-tool.ts.Agent flow moves from a blocking
POSTwith{ sessionId, task }toToolLoopAgentstreaming viatoUIMessageStreamResponse(); the UI usesuseChatandAgentStepsSidebarso playwright steps, code, timing, results, and errors appear live. Follow-up tasks reuse the same browser session;sessionStoragerestores session metadata on refresh.Browser API tweaks: smaller viewport, 30-minute session timeout, and region/stealth on create. README and deploy URLs point at
kernel/kernel-nextjs-template; docs call out that/api/agentis unauthenticated on public deploys.Reviewed by Cursor Bugbot for commit 3ddfa6b. Bugbot is set up for automated code reviews on this repo. Configure here.