Skip to content

feat: add marko/debugbar package#43

Merged
markshust merged 6 commits into
marko-php:developfrom
ps-carvalho:feature/marko-debugbar-package
May 1, 2026
Merged

feat: add marko/debugbar package#43
markshust merged 6 commits into
marko-php:developfrom
ps-carvalho:feature/marko-debugbar-package

Conversation

@ps-carvalho
Copy link
Copy Markdown
Contributor

@ps-carvalho ps-carvalho commented Apr 27, 2026

Summary

Adds the Marko Debugbar package to the monorepo as packages/debugbar.

This imports the committed standalone debugbar package from d89df8e (feat(profiler): enhance toolbar UI and request detail display) and adapts it to the monorepo package conventions.

The package provides a development debugbar and request profiler with collectors for request, response, timing, memory, messages, database, logs, views, Inertia, and config data.

Type of Change

  • New feature

Related Issues

Closes #

Implementation Notes

  • Adds packages/debugbar with source, config, module registration, tests, README, .gitattributes, and MIT license.
  • Uses self.version for all marko/* dependencies.
  • Keeps package type marko-module, PSR-4 autoloading, helper file autoloading, and extra.marko.module metadata.
  • Adds a root path repository and root test autoload namespace for monorepo development.
  • Adds marko/debugbar to the root require-dev, not require, so the package is available for monorepo tests without becoming part of the default runtime dependency set.
  • Adds debugbar to bug report and feature request package option lists.
  • Updates package-count assertions from 71 to 72 for the current develop package set.

Compatibility

This PR is intended to be non-breaking:

  • Existing Marko runtime package requirements are unchanged.
  • marko/debugbar is optional and opt-in for consuming applications.
  • Debugbar capture is gated by debugbar.enabled, which defaults to APP_DEBUG through the package config.
  • The root monorepo includes the package only as a development requirement so its tests and autoloading are available in this repository.

Validation

  • composer validate --no-check-lock
  • composer update marko/debugbar --with-dependencies --ignore-platform-req=ext-imagick
  • composer dump-autoload
  • vendor/bin/pest packages/debugbar/tests
  • vendor/bin/pest tests/PackagingTest.php tests/IntegrationVerificationTest.php --exclude-group=integration-destructive
  • composer test
  • vendor/bin/phpcs --standard=phpcs.xml packages/debugbar
  • git diff --check

Note: the local environment is missing ext-imagick, so Composer update was run with --ignore-platform-req=ext-imagick. Full monorepo composer cs:check is currently blocked by existing baseline/config issues outside this package; the new debugbar package passes PHPCS directly.

Checklist

  • Tests pass (composer test)
  • Package lint passes (vendor/bin/phpcs --standard=phpcs.xml packages/debugbar)
  • Follows code standards

@github-actions github-actions Bot added enhancement New feature or request breaking Introduces a breaking change labels Apr 27, 2026
@ps-carvalho ps-carvalho force-pushed the feature/marko-debugbar-package branch from a6b8601 to 1752327 Compare April 27, 2026 21:48
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Apr 27, 2026
@ps-carvalho ps-carvalho changed the title feat(debugbar): add debugbar package feat: add debugbar package Apr 27, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Apr 27, 2026
@ps-carvalho ps-carvalho changed the title feat: add debugbar package feat: add marko/debugbar package Apr 27, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Apr 27, 2026
@markshust markshust removed the breaking Introduces a breaking change label May 1, 2026
…s page

- Slim README to the slim-pointer format from docs/DOCS-STANDARDS.md
- Add docs/src/content/docs/packages/debugbar.md per DOCS-STANDARDS:174
- Strip module.php to the canonical singletons + boot shape used by
  vite/errors-simple. Drop redundant `enabled: true` (default), drop
  `sequence: { after: ... }` (DI resolves lazily — boot runs after all
  bindings register), drop binding closures with defensive `instanceof`
  checks on container resolutions (the container is type-guaranteed —
  see .claude/pr-review-process.md "No defensive checks on container
  resolutions"). Autowiring resolves Debugbar(ConfigRepositoryInterface,
  ?DebugbarStorage, HtmlDebugbarRenderer) and DebugbarStorage(Config,
  ProjectPaths) directly.
- Reformat packages/debugbar/composer.json to 4-space indent to match
  every other package's composer.json
- Apply php-cs-fixer brace-style fixes across the package
- Bump PackagingTest / IntegrationVerificationTest counts 72 → 73 to
  account for both the marko/vite (merged on develop after this branch
  was cut) and marko/debugbar packages

Co-Authored-By: Paulo Carvalho <paulofrediani@icloud.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@markshust markshust force-pushed the feature/marko-debugbar-package branch from 1752327 to 5c0a26d Compare May 1, 2026 15:14
markshust and others added 2 commits May 1, 2026 12:34
Replace `try { $config->get($key); } catch (Throwable) { return $default; }`
in Debugbar, DebugbarStorage, and ProfilerController with an explicit
`has()` check followed by `get()`. This honors Marko's loud-errors
principle (.claude/pr-review-process.md): missing-key is the only case
that should silently fall back to the default — any other failure
(broken config provider, etc.) bubbles up loudly instead of being
swallowed by a blanket Throwable catch.

The intentional Throwable catch in Debugbar::store() is retained: it
guards storage I/O, not config lookup, and the existing comment makes
the "debug tooling must never break the response" intent explicit.

Drop now-unused `Throwable` imports from DebugbarStorage and
ProfilerController.

Co-Authored-By: Paulo Carvalho <paulofrediani@icloud.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…etadata

- Remove the route.prefix config option (config + docs + Debugbar::profilerUrl)
  and hardcode the profiler URL prefix to /_debugbar. The knob was a lie:
  the actual ProfilerController routes are PHP attributes
  (#[Get('/_debugbar/{id}')]) fixed at compile time, and HtmlDebugbarRenderer
  / ProfilerPageRenderer hardcode /_debugbar in their generated links.
  Setting DEBUGBAR_ROUTE_PREFIX only changed the X-Marko-Debugbar-Url
  header, breaking the toolbar's Open button. Honor the loud-errors
  principle: remove the option rather than ship a configuration that
  silently lies. The single source of truth is now /_debugbar across
  routes, headers, links, and rendered URLs.

- .gitattributes: trim entries for files this package does not contain
  (phpstan.neon, phpstan-bootstrap.php, .php-cs-fixer.php), and
  column-align to match packages/vite/.gitattributes and
  packages/dev-server/.gitattributes.

- Remove tests/bootstrap.php — dead code in the monorepo. The root
  phpunit.xml does not reference it, and its env() polyfill duplicates
  the one autoloaded from marko/env (already required by the package).

- Drop the now-unreachable 'prefix' entry from DebugbarTest config setup.

Co-Authored-By: Paulo Carvalho <paulofrediani@icloud.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@markshust
Copy link
Copy Markdown
Collaborator

Thanks @ps-carvalho — landing this on top of the vite work is great. The plugin wiring around ConnectionInterface / LoggerInterface / ViewInterface is exactly the right seam, and the #[Plugin] attribute usage is clean.

I rebased onto current develop and pushed three follow-up commits to align with the package conventions in .claude/pr-review-process.md. Sharing the rationale here so the patterns are clear for future PRs:

What changed and why

5c0a26d — package conventions + docs page

  • README slimmed to the title + Installation + Quick Example + docs link shape (~25 lines). Per docs/DOCS-STANDARDS.md, package READMEs are slim pointers; the deep content moves to the docs page. Compare packages/vite/README.md and packages/dev-server/README.md.
  • Added docs/src/content/docs/packages/debugbar.md — required for every package per DOCS-STANDARDS:174.
  • module.php reduced to the canonical singletons + boot shape (matches packages/vite/module.php and packages/errors-simple/module.php):
    • Dropped 'enabled' => true (default).
    • Dropped sequence: { after: ['marko/config', 'marko/routing'] }. Marko's container resolves lazily and boot callbacks fire after all modules register their bindings, so config and routing are already available when Debugbar::boot() runs. Verified in Marko\Core\Container\Container::call().
    • Dropped the bindings closures with if (! $x instanceof Foo) throw RuntimeException checks — the container is type-guaranteed after $container->get(FooInterface::class), so those throws are validation for impossible states (see "No defensive checks on container resolutions" in pr-review-process.md). With the closures gone, autowiring resolves Debugbar(ConfigRepositoryInterface, ?DebugbarStorage, HtmlDebugbarRenderer) and DebugbarStorage(Config, ProjectPaths) directly.
  • composer.json reformatted to 4-space indent to match the other 70+ packages.
  • php-cs-fixer brace-style fixes across the package (multi-line method signatures collapsed).
  • Bumped PackagingTest / IntegrationVerificationTest counts 72 → 73 to account for both marko/vite (merged on develop after this branch was cut) and marko/debugbar.

3e6a68e — replace try/catch config lookups with has() guards
Eight sites in Debugbar, DebugbarStorage, and ProfilerController had:

try { $value = $this->config->get($key); }
catch (Throwable) { return $default; }

Marko's loud-errors principle (pr-review-process.md line 73) flags this pattern: a missing key should fall back to the default, but anything else (a broken config provider, an unexpected scope, a serialization failure) should bubble up loudly instead of being silently swallowed by a blanket Throwable catch. Replaced with explicit if (! $this->config->has($key)) return $default; + a plain get() after.

The intentional Throwable catch in Debugbar::store() is retained — it guards storage I/O (not config lookup), and the existing comment makes the "debug tooling must never break the response" intent explicit.

f99e1e6 — drop broken route.prefix knob, clean up package metadata

  • Removed the route.prefix config option. It was a lying config: only Debugbar::profilerUrl() honored it, but the actual routes are PHP attributes (#[Get('/_debugbar/{id}')]) fixed at compile time, and HtmlDebugbarRenderer / ProfilerPageRenderer hardcode /_debugbar in their generated links. Setting DEBUGBAR_ROUTE_PREFIX only changed the X-Marko-Debugbar-Url header — the toolbar's "Open" button would point at the wrong URL. Path of least surprise: hardcode /_debugbar everywhere as the single source of truth. If you ever need a configurable prefix, registering routes in a boot callback (instead of via attributes) is the way.
  • Trimmed .gitattributes to list only files the package contains, and column-aligned to match packages/vite and packages/dev-server.
  • Removed tests/bootstrap.php — dead code. The root phpunit.xml doesn't reference it, and its env() polyfill duplicates the one autoloaded from marko/env (already required).

Status

  • composer test — 4844 passed
  • phpcs + php-cs-fixer clean on touched files
  • composer validate clean
  • Removed the breaking label — this is purely additive

Thanks again for both this and marko/vite — these are landing back-to-back nicely.

@ps-carvalho
Copy link
Copy Markdown
Contributor Author

Cheers @markshust plenty more to come :)

markshust and others added 2 commits May 1, 2026 13:17
…arget)

Three changes to the injected toolbar markup:

1. Brand mark is now the inline pixel-art Marko M (the favicon SVG from
   docs/public/favicon.svg) instead of the literal text "M". Wrapped in
   <a href="https://marko.build" target="_blank" rel="noopener noreferrer">
   so the trademark links to the framework site. SVG is inlined (no
   external asset dependency, no extra request).

2. Panel now has a fixed height (default 320px, persisted in
   localStorage as `marko-debugbar-height`) instead of `max-height: 46vh`
   shrinking to fit content. The previous behavior caused the toolbar to
   visibly jump as you switched between tabs with different content
   sizes. New structure stacks `[handle, panel, bar]` in source order so
   the panel grows upward from the bar, which sits at the viewport
   floor. A 6px ns-resize drag handle appears at the top of the panel
   when expanded; pointer-event-based drag resizes the panel and saves
   the height to localStorage on pointerup. Height is clamped to
   [120px, window.innerHeight - 100px] and re-clamped on window resize
   so the handle stays reachable. Active tab and expanded state also
   persist (`marko-debugbar-tab`, `marko-debugbar-expanded`).

3. Replaced the "Open" button (which linked to the per-request profiler
   page `/_debugbar/{id}`) with "All requests" linking to `/_debugbar`.
   The per-request page renders the identical collector data the
   toolbar already shows inline, so opening it from a page that already
   has the toolbar was just duplication. The index page (list of
   captured requests with the ability to switch between them) is the
   actually-useful navigation target. Per-request URL is still exposed
   via the `X-Marko-Debugbar-Url` response header for non-HTML clients.

Drop the now-unused $profilerUrl local in HtmlDebugbarRenderer::render.
Update DebugbarTest assertion from `/_debugbar/{id}` (old Open href) to
`href="/_debugbar"` + "All requests" text (new link target).

Co-Authored-By: Paulo Carvalho <paulofrediani@icloud.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The X close button called root.remove() to remove the toolbar from the
current DOM, but the toolbar is re-injected on every subsequent
response — so the "close" was misleading: it dismissed for one page,
then came right back. That violates the no-pseudo-functionality
principle (CLAUDE.md): the affordance suggests permanence but doesn't
deliver it.

The Collapse toggle already covers the legitimate "minimize this so I
can see the page" use case (shrinks to the rail). Users who want the
debugbar genuinely off should set DEBUGBAR_ENABLED=false (or not
install the package at all). Removed the close button, its CSS rule,
and the click handler.

Co-Authored-By: Paulo Carvalho <paulofrediani@icloud.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@markshust
Copy link
Copy Markdown
Collaborator

Two more commits on top of today's review fixes — both UX refinements to the injected toolbar after testing locally against ~/Sites/my-app-debugbar-local.

8919e57 — toolbar UX: logo, resize, link target

Three changes bundled together:

  1. Brand mark. The literal text "M" was a placeholder — replaced with the actual pixel-art Marko trademark inlined from docs/public/favicon.svg (20 rects, ~600 bytes), wrapped in <a href="https://marko.build" target="_blank" rel="noopener noreferrer">. No external asset, no extra request.

  2. Stable panel height with resize. The panel had max-height: 46vh shrinking-to-fit content, which made the toolbar visually jump every time you switched tabs (a 0-row table tab vs. a 200-row queries tab = wildly different bar position). Restructured to a fixed-height panel that grows upward from the bar at the viewport floor:

    • Source order is now [handle, panel, bar] so the panel sits above the bar visually
    • Default panel height: 320px
    • 6px ns-resize drag handle at the top of the panel (only visible when expanded)
    • Pointer-event-based drag with setPointerCapture, clamped to [120, window.innerHeight - 100], re-clamped on window.resize so the handle stays reachable
    • Three localStorage keys persist across reloads: marko-debugbar-height, marko-debugbar-expanded, marko-debugbar-tab
  3. "Open" → "All requests". The Open button linked to /_debugbar/{id} (the per-request profiler page), but ProfilerPageRenderer::show() renders the identical collector data the toolbar already shows inline — clicking it from a page that already has the toolbar was just duplication. Replaced with "All requests" linking to /_debugbar (the index of stored snapshots, which is useful for navigating between requests). The per-request URL is still exposed via the X-Marko-Debugbar-Url response header for non-HTML clients.

7e8aa1f — remove the close button

Dropped the X button. It cluttered the right side of the bar and wasn't super intuitive — "close" suggested a permanent dismiss, but it just removed the toolbar from the current DOM (it came back on the next request anyway). The Collapse toggle already handles the legitimate "minimize this so I can see the page" use case, and users who actually want the debugbar off should set DEBUGBAR_ENABLED=false or skip installing the package.

Status

  • composer test — 4844 passed
  • phpcs + php-cs-fixer clean on touched files
  • Verified locally: panel height persists across hard reloads; switching tabs no longer changes the bar's vertical position; the M trademark links out to marko.build; the Open→All-requests change makes the toolbar a launch point for the index instead of a redirect to the same data

@markshust markshust merged commit 518ae68 into marko-php:develop May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants