Skip to content

Redesign the playground and homepage - #143

Merged
alies-dev merged 46 commits into
psalm:masterfrom
alies-dev:playground-redesign
Nov 8, 2026
Merged

alies-dev merged 46 commits into
psalm:masterfrom
alies-dev:playground-redesign

Conversation

@alies-dev

@alies-dev alies-dev commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Redesigns the playground and the homepage.

  • Layout: editor and output side by side with a draggable split, a toolbar on top and a status bar below. Settings open in a drawer, with links to the config docs.
  • Error level: chosen in Settings (default 2), saved with snippets and accepted as ?level= on /check and snippet result URLs; snippet JSON includes error_level. A non-default level is part of the snippet hash, so default-level links are unchanged.
  • Output: the Issues tab shows each issue's error level and marks auto-fixable ones; a new Types tab lists inferred types; taint traces are numbered steps. Hovering an item highlights its code.
  • Editor: Ctrl/Cmd+click goes to a definition (php.net for built-ins), signature help while typing a call, auto-closing brackets, #L5-L12 line links. Psalm's byte offsets are converted to character offsets, so issue markers no longer shift after non-ASCII text.
  • Shortcuts: Ctrl/Cmd+Enter runs, Ctrl/Cmd+S shares, F8 steps through issues.
  • Fix: "Fix N issues" shows Psalm's fix as a diff to apply or cancel; after applying, the toolbar toggles between the original and fixed code.
  • Share copies the link and stays on the page.
  • /play is the full-page playground; /playground, /demo, /try, /sandbox and bare /r redirect there (/r/<hash> is unchanged).
  • Homepage: 1024px wide again, with a compact widget (editor, Expand, Share), a Get started button and articles from the last 14 days.
  • Nav: Docs · Playground · Plugins · Blog · Paid support · GitHub icon; page titles (support, blog, docs home) match it.
  • Stylesheets and scripts are versioned by file mtime instead of hand-bumped ?N keys.

Deploy: run the phinx migration (codes.error_level) before deploying the code; saving snippets needs the column.

Known gap:

  • Error level 1 still shows MixedAssignment and a few other Mixed issues as info (forced in OnlineChecker).

… status bars

The editor sits left and the output right, below a bar holding the PHP version select, Settings and Share, above a status bar with the run state and Psalm version. Markup is shared by the homepage and snippet pages via includes/playground.php, and the styles live in a hand-written assets/css/playground.css that overrides the legacy rules in site.css. Expand/Shrink are gone; their functions are kept as no-ops.
Drag, arrow keys or double-click (reset) on the splitter; the split is kept in localStorage when it is available.
Cmd/Ctrl+Enter re-runs the analysis, Cmd/Ctrl+S shares, F8 and Shift+F8 jump to the next and previous issue (wrapping). Issue locations in the output also jump to the issue.
…efaults

Settings open as a right-hand column instead of replacing the output, split into Unused code, Inference and Checks, with a Reset button. The Settings button reads "Settings · N changed" when the settings differ from Settings::defaultValues(). Toggling no longer rebuilds the panel, so keyboard focus stays on the checkbox.
…tus bar

Jumping to an issue scrolls only the output pane and marks the issue with a black rule. Below 600px the status items wrap onto their own lines.
#L5 or #L5-L12 highlights editor lines on / and /r/<hash>; clicking a line
number sets the fragment (Shift+click extends, re-click clears) via
replaceState. The highlight follows edits through CodeMirror line handles,
Share keeps the fragment in the copied link and address bar, and popstate
no longer reloads when only the fragment changed.
- Share is the only filled button and shows "Copied ✓", "Copy failed"
  or "Share failed" for 2s; the status bar message stays.
- Fix code becomes a ghost button. It is kept: it rewrites the code,
  while the "fixable issue(s)" link in the results opens the docs.
- Config on the left (compact "PHP 8.6" picker, Settings next to it),
  actions on the right; the full explanation moves to the picker's
  title and aria-label, and the drawer header shows the version.
- Slow runs (>150ms) dim the output and show "running…" in the tabs bar.
- An empty editor says "Write some PHP to analyse".
- "Skip to editor" link on playground pages; Share tooltip names the
  platform shortcut.
…nd page

The homepage playground sits next to the intro again (stacked editor over output) with an Expand/Shrink toggle that keeps code, results, settings and the #L fragment. /playground and /r/<hash> open expanded and have no toggle. expandCode() and shrinkCode() work again on the homepage and stay no-ops elsewhere.
Tighten the gap between nav items so seven links fit on one row at 1024px.
/playground now shows only the nav and the playground, which fills the viewport (no heading, no footer). Each setting in the drawer links to its psalm.xml option in the configuration docs, the status bar links the issues, annotations, types, taint analysis and configuration docs, and issue type links get a title.
/check already returns Psalm's IssueData::error_level (the issue class ERROR_LEVEL), so this is client side only. Each issue shows a muted "level N", "any level" (-1, 8) or "feature" (-2) linking to its section of the error levels docs, with a tooltip saying at which errorLevel it is an error. Bug reports include it too.
Per-setting docs links and issue type links stay.
Psalm reports issues in analysis order. The list showed that order while F8 jumped by position, so the highlight hopped around the list, and F8 with the cursor on an issue skipped it. Issues are now sorted by line, column, errors before info, then type, and the list, F8/Shift+F8, lint markers and bug reports all use that order.
…n the status bar

Uses the version_tag, version_compare_url and version_commit fields from psalm#127; falls back to version_label when no tag was resolved.
Text nodes directly inside the flex status item became anonymous flex items and lost their leading and trailing spaces, so it read "Psalm7.0.0+2(ca4690d)". Render the label inside one inline wrapper.
…the toolbar

Share, Fix code and Report bug on the left; the PHP picker, Settings (above its drawer) and Expand/Shrink on the right. On narrow screens the actions row comes first.
The playground always ran at errorLevel 2, Psalm's default when psalm.xml
has none, and never said so. Settings now offer levels 1 to 8 with 2 as the
default, sent as `level` to /check and /add_code and kept in ?level= like ?php=.

Snippets store the level in a new nullable `error_level` column; NULL (older
snippets) runs at 2. The default level is left out of the snippet hash, so
existing links still dedupe. /r/<hash>.json returns the stored level and
/r/<hash>/results accepts ?level=.
The toolbar picker right above already shows it.
/playground, /r, /demo, /try and /sandbox redirect to /play. The nav is
now Docs, Playground, Plugins, Blog, Paid support and a GitHub icon;
"Get started" moves to a button on the homepage.
asset_url() appends ?v=<mtime>, replacing the hand-bumped ?N keys.
The homepage is 1024px wide again with a 492px playground widget, as on
the old site. Articles from the last 14 days (up to three) are listed
under the widget, or under the intro on narrow screens.
"Paid support for Psalm" on /support, "Blog - Psalm" for the blog index
and "Psalm documentation" for the docs home.
views/play.php only set $playground_page and required index.php, so
Psalm analysed index.php with that variable fixed to true and reported
every homepage branch as dead. The baseline now also lists the
template variables that includes/playground.php reads from its
includer.
@alies-dev alies-dev self-assigned this Nov 8, 2026
Comment thread includes/playground.php Fixed
Comment thread includes/playground.php Fixed
Comment thread src/OnlineChecker.php Fixed
Comment thread views/add_code.php Fixed
Comment thread views/add_code.php Fixed
Comment thread views/index.php Fixed
Comment thread views/snippet.php Fixed
Comment thread views/snippet.php Fixed
Comment thread views/snippet.php Fixed
Comment thread views/snippet.php Fixed
…te request input

Clears the Psalm alerts the PR's code scanning raised on touched lines: the
include-scope variables become function parameters, check.php rejects
non-string code/settings and parses the fix flag as a boolean, and the
signature label no longer concatenates a nullable method name.
Comment thread views/snippet.php Fixed
The not-found branch now exits explicitly, so Psalm knows $result is the
row array afterwards instead of mixed.
It bypassed the error-level validation and lacked the navigation data, and
its hash matches neither the current nor the previous homepage example.
Rollback replays change() to record what to invert, so the existing-column
guard must only apply when migrating up.
…ed symbol field

Functions Psalm knows only from its callmap (e.g. ctype_digit) have no
FunctionStorage; fall back to the callmap entry.
…te consistent

- Non-ASCII text before an issue no longer shifts markers, links and hints
- Share saves the PHP version and level it sent, even if changed meanwhile
- A stale failed fix request no longer overwrites the run state
- Rename navigation to codeNavigation so it does not shadow window.navigation
Also fixes the Expand button disappearing at 1000-1021px widths.
@orklah

orklah commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Seems good!

One thing to discuss though, there used to be a specific cache for the code example on the root page. It disappears with this PR.
That specific code is pretty simple and it seems doubtful that we will change its result often so the cache seemed a good idea

That cache was persistent so it was never checked, this could be an issue for long period of times but it could probably put in a CI somewhere instead.

Without this cache, the number of analysis is bound to increase a lot

@alies-dev

alies-dev commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Good catch, but that cache wasn't being hit: its hash (6ca8d27d...) doesn't match the homepage example on master (65095c36…), and production /check currently returns a live result (Psalm 04cec8e…, 5 issues vs 4 in the cached JSON). It also predates the navigation data and error level, so it would have bypassed the new level validation.

I agree the homepage example is worth caching though. I'll add a small cache in check.php for that example with default settings, keyed by code hash, settings, PHP version, level and Psalm version, so it refreshes itself after a Psalm update without needing CI.

Every homepage view analyzes the same example. Its results are now stored
in a temp-dir file keyed by settings, PHP version, level, fix flag, the
installed Psalm version and the checker's mtime, so they refresh on their
own after a Psalm update or deploy. The example code moves to
HomepageExample::CODE so the page and the check share one copy.
Comment thread src/HomepageExample.php Fixed
Arbitrary settings keys from a client would otherwise create unlimited cache
files. The playground always sends every known setting as a boolean, so its
requests still hit the cache.
@alies-dev

alies-dev commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor Author

cache: done in fbae1db and e9767da: any combination of the known boolean settings is cached, unknown keys bypass the cache.

@alies-dev
alies-dev merged commit b79ec8e into psalm:master Nov 8, 2026
2 checks passed
@alies-dev
alies-dev deleted the playground-redesign branch November 8, 2026 12:56
alies-dev added a commit to alies-dev/psalm.dev that referenced this pull request Nov 8, 2026
Production deploys don't run composer scripts, so the codes.error_level
migration from psalm#143 has most likely not run; add_code.php writes that
column, so saving snippets needs it.
alies-dev added a commit that referenced this pull request Nov 8, 2026
Production deploys don't run composer scripts, so the codes.error_level
migration from #143 has most likely not run; add_code.php writes that
column, so saving snippets needs it.
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