1
0
mirror of https://github.com/alexandrev/xslt-lab.git synced 2026-09-18 19:43:16 +00:00
Commit Graph

185 Commits

Author SHA1 Message Date
alexandrev-tibco c48acd1a8c Add sticky sidebar ad to blog articles, fix EA script loading
- Article pages (single.html): two-column grid layout — article + 160px
  sidebar with sticky EA ad (id="xsltplayground-blog")
- Non-article pages (home, list): keep existing sticky header bar
  (id="xsltplayground-blog-header")
- Move EA script to <head> in baseof.html so it loads on all pages
  regardless of placement; remove script tag from ethicalads partial
- has-ethicalads body class now only applied on non-article pages
  (no 64px header-offset shift on article pages)
- Sidebar hidden on viewports <= 860px (mobile/tablet)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:22:52 +02:00
alexandrev-tibco a07f9df934 Move EthicalAds to inline result pane, drop A/B test, optimize SEO title
- Replace fixedheader/stickybox A/B with a single inline placement inside
  the result pane (before the editor, no data-ea-style) to improve view rate
- Remove adPlacement random split and derived variables (isCompactEthicalAd,
  ethicalAdStyle, ethicalAdHeight, ethicalAdVariant)
- Drop createPortal import (no longer needed)
- SEO: swap title order to "Online XSLT Editor, Tester & Validator — XSLT Playground"
  and rewrite meta description leading with "Test XSLT online" for higher CTR
  on queries: xslt online, xslt tester, xslt validator
- Bump chart to 0.1.4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 14:37:53 +02:00
alexandrev-tibco 28f5ea981a Add undo button for format action in editor and result pane
After formatting XSLT or result XML, an undo button appears next to
the sparkles icon. Clicking it restores the pre-format content.
Undo state resets on new transform or tab switch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 12:49:14 +02:00
alexandrev-tibco fd7bf2c799 Increase transform debounce from 500ms to 2000ms
Prevents overwhelming the backend with requests on every keystroke.
Users reported slowdown when typing — 2s gap feels natural and still
gives near-instant feedback after finishing edits.
Update test timeouts accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 12:40:45 +02:00
alexandrev-tibco b0569736d4 Restore A/B test header vs sidebar, move BuyMeACoffee to left
- 50/50 random per session: header (xsltplayground-main fixedheader)
  vs sidebar (xsltplayground-params stickybox via portal)
- BuyMeACoffee moved to Left position to avoid collision with stickybox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 16:31:40 +02:00
alexandrev-tibco b5736c51af Add frontend test step to CI before Docker build
Runs npm test (vitest) as a required job before build-and-push.
Catches runtime import errors like the createPortal/react-dom mistake.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:12:18 +02:00
alexandrev-tibco b61669308a Fix createPortal import: use react-dom instead of react
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:10:21 +02:00
alexandrev-tibco 09a0978cff Switch to xsltplayground-params stickybox as sole ad placement
Remove A/B test and xsltplayground-main fixedheader. Use only
xsltplayground-params (stickybox) rendered via createPortal to document.body,
always active. Higher eCPM ($1.66 vs $0.82) observed vs fixedheader.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:27:55 +02:00
alexandrev-tibco 33d8c0ec84 Fix sidebar ad variant using createPortal to escape overflow:hidden container
The stickybox div was being crushed to 0 height inside app-container
(height:100% flex column). Use createPortal to mount it at document.body
so EthicalAds can position it freely as a floating widget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:59:28 +02:00
alexandrev-tibco 610a3f8741 Fix A/B ad placement: move sidebar ad outside collapsed params panel
The sidebar ad was inside the paramsCollapsed conditional block, so
when params were collapsed ethicalSlotRef was null and the ad never loaded.
Move both ad variants to the root level of the component so they're
always in the DOM regardless of panel state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 11:17:25 +02:00
alexandrev-tibco 43ecf16340 A/B test ad placement: header vs sidebar (50/50 random per session)
Each page load randomly assigns adPlacement='header' or 'sidebar'.
- header: xsltplayground-main fixedheader (current behavior)
- sidebar: xsltplayground-params stickybox in the params panel
Never shows both simultaneously, complying with EthicalAds single-ad policy.
GA4 event 'ad_placement_variant' tracks which variant each user sees.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 13:17:22 +02:00
alexandrev-tibco 5d81b58e2f Remove duplicate EthicalAds placement to comply with publisher policy
Single ad per page policy violation: remove xsltplayground-params placement
from the params panel, keeping only xsltplayground-main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 12:54:10 +02:00
alexandrev-tibco 12ba1c7de9 Fix param block hiding on editor focus
Always show injectParamBlock() in the editor — no more switching value
on focus/blur. onChange already calls stripParamBlock() before saving,
so editing is safe. Removes editorFocused state entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 12:07:35 +02:00
alexandrev-tibco 9ddf5b5c6b Copy link button: place right next to Success text 2026-04-21 12:02:29 +02:00
alexandrev-tibco 45e0895a76 Share button: integrated styling + error panel + clearer text
- Button blends into green/red bar (no separate border/background)
- Text changed to "🔗 Copy link" — unambiguous, not "uploading"
- Also shown in error panel header (red context styling)
- Ad retry only fires if slot is still empty after 1.5s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:57:29 +02:00
alexandrev-tibco 782973e8f3 Share button polish + ad retry fix
- Share button only appears after user's own transform (not auto-run)
- Clipboard text includes motivating message: "Check out my XSLT transformation! "
- Success bar still spans full width; share button floats right aligned
- EthicalAds main slot retries load() after 1s to fix intermittent race condition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:40:24 +02:00
alexandrev-tibco b200e81746 Add share button, OG image, and share_transform GA4 tracking
- OG image (1200x630) generated from brand assets for Slack/Teams unfurls
- Twitter card + og:image:width/height meta tags in index.html
- "Share transform" pill button appears next to "Success" after each run:
  copies shareable ?xslt=...&xml=... URL to clipboard
- Fires gtag share_transform event with xslt_version dimension
- CSS: gradient pill, hover lift, green "copied" state, dark mode support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:32:07 +02:00
alexandrev-tibco 0dd55ba75d EthicalAds: refresh every 30s when visible (both placements)
Remove transform-count gate — ad now refreshes every 30s if visible,
regardless of user activity. Improves view count for users who do few
transforms (e.g. blog tryUrl arrivals).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:26:24 +02:00
alexandrev-tibco 688ea2f0a3 Fix xpath-document tryUrl: replace document('colors.xml') with inline xsl:key lookup
Colors data embedded in the XML input document, avoiding the local
file reference that fails in the sandbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:45:20 +02:00
alexandrev-tibco 7690ab0cac Fix tryUrl auto-run + add doc() sandbox warning
- autoRunReady initializes to true when page loads from ?xslt= URL,
  so the transform fires immediately without requiring a click
- Add doc(uri) to completions list (XSLT 2.0+) with note that only
  HTTP/HTTPS URLs are supported in the playground
- Show a yellow hint in the error panel when FODC0002/I-O error
  detected, explaining that local file paths are not supported

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:42:55 +02:00
alexandrev-tibco 19bfe481e9 Fix merge article tryUrls: replace doc() with inline xsl:variable data
The 6 xsl:merge-related articles had tryUrls that failed at runtime because
doc() tried to load external XML files that don't exist in the backend sandbox.
Replaced with self-contained XSLTs using inline variable data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:25:07 +02:00
alexandrev-tibco bf645b5378 Revert foldGutter; move feedback widget top-right; add email field
- Revert foldGutter: false — was causing comments to appear hidden
  when editor was focused (fold markers + codeFolding() interference)
- FeedbackWidget: default position now top-right at editor height
  (STORAGE_KEY bumped to feedbackPos2 to reset saved position)
- FeedbackWidget: add optional email input mapped to 'mail' in webhook

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 12:57:18 +02:00
alexandrev-tibco 3c9b82d56b Fix Full docs link: use pointerdown + window.open
Replace passive anchor click with explicit pointerdown handler that
opens the URL via window.open. This fires before any tooltip-close
logic can intercept the interaction, and stopPropagation prevents
CodeMirror from treating it as an editor event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 12:09:37 +02:00
alexandrev-tibco 13a85804b2 Add placement id to params panel EthicalAds unit
Adds id="xsltplayground-params" so this unit is tracked separately
from the main "xsltplayground-main" placement in EthicalAds analytics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:45:16 +02:00
alexandrev-tibco 6316bc2a1d Fix Full docs link in hover tooltip; enable fold gutter
- Add mousedown stopPropagation on tooltip wrapper so CodeMirror doesn't
  intercept clicks and block "Full docs →" link navigation
- Enable foldGutter in editor basicSetup so expand/collapse controls
  appear in the line number gutter area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:40:02 +02:00
alexandrev-tibco 798ab314a0 Fix params-ad overflow, update copyright year and author link
- Fix EthicalAds second unit: switch text→image type, add overflow:hidden
  and flex centering to prevent ad from bleeding outside params column
- Copyright: 2025 → 2026
- Author name now links to alexandre-vazquez.com

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:30:00 +02:00
alexandrev-tibco 7ebf9d26ec Add push_and_deploy.sh script for CI-gated deploys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:24:40 +02:00
alexandrev-tibco 107592067d Add second EthicalAds text unit in params panel footer
Adds a text-type ad at the bottom of the data pipeline (params)
column, visible when the panel is open. Only renders when EthicalAds
is enabled and loaded, so no impact on localhost or ad-blocked users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:21:49 +02:00
alexandrev-tibco a5bfd053c8 Reduce EthicalAds reload frequency to improve view rate
Changed reload trigger from every-transform+30s to:
- Requires ≥3 transforms since last reload
- Minimum 2 minutes between reloads (was 30s)
- Ticker interval also updated to 2 minutes

Active users were generating decisions on every transform without
proportional views, deflating the view rate metric.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:16:37 +02:00
alexandrev-tibco fd5caf8ff2 Add in-content EthicalAds text placement on reference pages
Adds a second ad unit (text type) just before the "Open live example"
CTA on all /xslt/functions/ pages. Increases ad visibility on long
reference pages where the sticky header unit may scroll out of view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:07:08 +02:00
alexandrev-tibco 9420a2b094 Improve backend performance and add loading state to result pane
Backend:
- Add JVM startup flags (-XX:TieredStopAtLevel=1, -XX:+UseSerialGC,
  -Xms32m, -Xmx256m) to reduce per-request JVM startup time by ~30-50%
- Replace deprecated io/ioutil.TempDir with os.MkdirTemp (Go 1.17+)

Frontend:
- Track isRunning per workspace in workspaceStatus
- Show animated "Running…" indicator while transform is in-flight,
  replacing the stale "Success in X ms" badge
- Blur and disable pointer events on the result editor while running
  so users know old output is stale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:31:08 +02:00
alexandrev-tibco 84dfaecf57 Fix CI: bump Hugo to 0.154.5 for subsection layout lookup
Hugo < 0.154 doesn't resolve layouts/xslt/functions/single.html for
content under content/xslt/functions/ — it falls back to _default.
Aligning CI with the local version fixes the reference page layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 11:43:40 +02:00
alexandrev-tibco a04beb0ec4 Add URL preload feature and live example CTAs on reference pages
- App: parse ?xslt=, ?xml=, ?version=, ?title= query params on load
  (base64url-encoded) to pre-populate the editor from external links
- App: Share button in XSLT toolbar generates a preload URL for the
  current tab and copies it to clipboard (icon flips to ✓ for 2s)
- Icon: add share and check icons
- Blog: 225 reference pages now include a tryUrl frontmatter field
  with the first example pre-encoded; layout renders "Open live
  example in XSLT Playground →" CTA pointing directly to that URL
- Script: scripts/add_try_urls.py to regenerate tryUrl fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 11:35:40 +02:00
alexandrev-tibco 53e90ef86e feat(blog): complete XSLT/XPath reference — 229 function pages
Full coverage of XSLT 1.0, 2.0 and 3.0 elements and XPath functions:
- 59 XSLT elements (xsl:stylesheet → xsl:use-accumulators)
- 170 XPath functions (1.0 node/string/numeric/boolean, 2.0 sequence/
  date/QName/string, 3.0 HOF/map/array/JSON/streaming)
Each page: description, parameters table, return value, 2 runnable
Saxon examples with input XML + stylesheet + output, notes, cross-links.
xsltCompletions.js: all 229 entries now have blogSlug for hover links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:28:05 +02:00
alexandrev-tibco b9d58ff7e0 feat(blog): XSLT/XPath function reference section (Phase 2)
- 19 reference pages under /xslt/functions/ covering:
  xsl:value-of, xsl:for-each, xsl:for-each-group, xsl:function,
  xsl:iterate, last(), position(), count(), concat(), contains(),
  substring(), sum(), key(), current(), string-join(), tokenize(),
  matches(), replace(), format-date()
- Each page: description, parameters table, return value, 2 runnable
  examples (input XML + stylesheet + output), notes, cross-links
- New Hugo section with reference-specific layout (version badges,
  syntax block, "Try in Playground" CTA, parameters table styles)
- Menu entry "Reference" → /xslt/functions/
- CSS: .ref-badge, .ref-syntax, .ref-index-list, .ref-try + table styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:38:55 +02:00
alexandrev-tibco c31e3b27f8 feat(editor): hover tooltips + attribute completion for xsl:* elements
- hoverTooltip: hovering any xsl:element or xpath function shows
  description, version badge, argument signature, and "Full docs" link
  (300ms delay, above cursor)
- Attribute completion: pass full ElementSpec[] schema to xml() via
  completeFromSchema(); covers all major xsl:* attributes with enum
  values where applicable (yes/no, validation modes, output methods…)
- Context-aware sources: custom source skips AttributeName nodes so
  attribute completion from the schema takes over cleanly
- ELEMENT_ATTRS map added to xsltCompletions.js: ~40 elements with
  their full attribute sets, filtered by active XSLT version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:38:21 +02:00
alexandrev-tibco f67b2ab991 feat(editor): XSLT/XPath autocomplete with version-aware filtering
- Add @codemirror/autocomplete with a custom completion source
- New xsltCompletions.js: ~80 xsl:* elements + ~120 XPath functions,
  each tagged with minVersion (1.0/2.0/3.0) and optional blogSlug
- Completions filter to the active XSLT version of each workspace
- Tooltip info() renders argument signature + "Full docs →" link
  pointing to blog.xsltplayground.com/xslt/functions/<slug>
  (placeholders until Phase 2 blog pages are written)
- Auto-close tags, XML linting and Format button also wired up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:23:24 +02:00
alexandrev-tibco c86026460c Perf: replace Monaco Editor with CodeMirror 6
Monaco loaded ~2MB from CDN on every visit causing LCP 8.8s.
CodeMirror 6 is bundled locally: 434KB (142KB gzip), no CDN dependency.

- @uiw/react-codemirror + @codemirror/lang-xml + @codemirror/theme-one-dark
- XML syntax highlighting, dark/light theme, readOnly, lineNumbers, wordWrap
- Removes CDN preload hints, loader config, Monaco external/chunk config
- Editor component simplified (no more lazy/Suspense/eager/requestIdleCallback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:12:05 +02:00
alexandrev-tibco e0f721a162 Helm: add feedbackWebhookUrl support to frontend deployment (chart 0.1.3)
Adds VITE_FEEDBACK_WEBHOOK_URL env var passed to frontend container,
wired to frontend.feedbackWebhookUrl in values.yaml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:09:22 +02:00
alexandrev-tibco 79e7d50ffc UX: replace feedback links with inline n8n webhook form
- Simple form with type selector (idea/bug/other) + textarea
- POSTs to VITE_FEEDBACK_WEBHOOK_URL (runtime env, set in Helm/entrypoint)
- States: idle → sending → success/error
- Removes email/GitHub links — no context switch for user
- Adds VITE_FEEDBACK_WEBHOOK_URL to entrypoint.sh env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 16:39:02 +02:00
alexandrev-tibco 9394853338 Perf: eager-load main XSLT editor and preload Monaco CDN assets
- Main XSLT editor uses eager=true to skip requestIdleCallback delay
  (it's the LCP element — must render ASAP)
- Secondary editors (XML inputs, result) keep deferred loading
- Preload Monaco CDN loader.js and editor.main.js in <head> so
  they start downloading before JS requests them

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:47:08 +02:00
alexandrev-tibco c50f23b23d UX: replace version pill with git commit SHA link to GitHub
The v0.2.0 label was meaningless. Now shows the short commit SHA
that links directly to the commit on GitHub. Injected at build time
via Vite (execSync git rev-parse --short HEAD).

Also removes CHANGELOG.md (unmaintained) and the changelogAnchor helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:31:19 +02:00
alexandrev-tibco 1f950a0a22 Perf: defer Monaco mount until browser idle to reduce TBT
All editors were mounting simultaneously on initial render, causing
Monaco to block the main thread (TBT 1520ms). Now uses requestIdleCallback
(timeout 1.5s) to defer Monaco initialization until after first paint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:24:15 +02:00
alexandrev-tibco 1d8faf6b3e Perf: add nginx gzip compression and cache headers
- gzip level 6 for JS, CSS, JSON, SVG, fonts
- immutable cache (1yr) for hashed JS/CSS assets
- 7-day cache for static assets (SVG, fonts, images)
- no-store for env.js (runtime config must be fresh)
- no-cache for HTML (SPA index)

Reduces JS transfer from ~460KB to ~115KB gzip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:23:46 +02:00
alexandrev-tibco 15127af247 Fix: use display:none on drop-hint file input to ensure label click works
The file-input CSS class (position:absolute 1px clip) was preventing the
label click from triggering the file picker in some browsers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:01:51 +02:00
alexandrev-tibco 2b87e1ffac UX: clicking drop-hint area opens file dialog for XML upload
Users were rage-clicking the "Drop your input XML files here" area
expecting a file picker. Converted to a label with hidden file input
supporting multiple XML files, same logic as drag-and-drop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:29:45 +02:00
alexandrev-tibco a6515606c0 Ads: only refresh when ad is visible (IntersectionObserver)
Refreshing every 30s regardless of visibility was causing low view rate
(23-38%) because the ad was often scrolled out of view. Now uses
IntersectionObserver (threshold 0.5) to gate both the interval refresh
and the post-transform refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:26:53 +02:00
alexandrev-tibco 9777436aea Perf: move EthicalAds div to end of DOM to fix LCP
Ad div was first in DOM causing it to be the LCP element (10.2s).
Moved to end of app-container and repositioned visually with order:-1
so the ad still appears at the top without blocking LCP measurement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:41:51 +02:00
alexandrev-tibco 4247dfc2de UX: clicking anywhere on Feedback header toggles the panel
Previously only the arrow button toggled the panel. Now the full header
acts as the toggle, with a guard to ignore clicks that were actually drags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 11:48:23 +02:00
alexandrev-tibco 12321a50c0 Ads: add placement ID and refresh on interval + transform
- Add id="xsltplayground-main" to EthicalAds div for Placements report
- Refresh ad every 30s via setInterval
- Also refresh on each successful transform (throttled to 30s minimum)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 11:29:45 +02:00