1
0
mirror of https://github.com/alexandrev/xslt-lab.git synced 2026-09-15 09:33:15 +00:00
Commit Graph

240 Commits

Author SHA1 Message Date
claude-code b2737ec715 SEO: fix internal linking, de-cannibalize, move blog to /blog/ subpath
Phase 1 — internal linking:
- Real [related] index (tags/keywords/category) in hugo.toml; rewrite
  related-posts.html to use .Related instead of "first 4 posts", which
  had left 12/20 posts with zero inbound links.
- Add tags to 6 previously untagged posts.

Phase 2 — de-cannibalization of the transactional cluster:
- Soft-301 mechanism (redirectTo -> meta refresh + noindex,follow) in baseof.
- xslt-online-editor-guide redirects to the app (pure duplicate of home).
- xslt-validator-online retargeted informational; drop the "common errors"
  section that cannibalized the errors hub, link to it instead.
- Migrate why-xslt-playground-online-editor into /about/ with an alias.
- Remove the duplicate <h1> on the app landing page.

Phase 3 — consolidate authority on the money domain:
- baseURL -> https://xsltplayground.com/blog/ (served via NPM reverse
  proxy to GitHub Pages; subdomain will 301 to the subpath).
- Rewrite internal blog links to /blog/... and cross-domain app links
  to xsltplayground.com/blog/...

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZntQUfw463NW4ftgSjWw5
2026-07-24 07:31:29 +00:00
claude-code 9bedbc3dc9 SEO: turn the string-functions roundup into a hub linking dedicated pages
GSC shows the dedicated function pages already rank well — xpath-contains
at pos 3.3, xpath-substring at pos 5.8 — but the string-functions roundup
post competed with them for the same terms and often outranked the better
page at a worse position (e.g. it took "substring" impressions at pos 17
while xpath-substring sat at pos 5.8). Classic self-cannibalization.

- string-functions: link every function in the at-a-glance table to its
  dedicated /xslt/functions/ reference page (18 of 19 exist). The roundup
  now consolidates authority into the pages that already win and keeps the
  head term "xslt string functions" for itself, instead of splitting
  signals across both.
- template-matching (ranks pos 5.4 for "template match", no cannibalism):
  link the "mode" and "apply-templates" mentions to their reference pages —
  it previously linked to nothing but the homepage.

Hugo build verified locally (345 pages, exit 0); links resolve.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-22 20:20:49 +00:00
claude-code c9078842d4 UX: fix the top dead-click zones Clarity flagged (38% of sessions)
Clarity showed dead clicks on 38% of sessions, all on desktop. Reproduced
the hotspots in a headless browser: the footer logo/brand, the "Input"
panel header text, and the "Input XML" label — all looked clickable but
did nothing.

- Footer logo + brand: wrap in <a href="/"> so it navigates home, the
  universal expectation for a site logo.
- "Input" header title: clicking it now toggles the panel collapse, the
  same action as the adjacent chevron button (keyboard-accessible too).
- "Input XML" label: clicking focuses the XML editor below it, like a
  real <label>.

Verified: none of the three register as dead clicks anymore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-22 19:47:47 +00:00
claude-code 9c6f81a043 Perf: hand-rolled CodeMirror to slim the critical editor chunk (-29KB gz)
@uiw/react-codemirror's basicSetup statically imports @codemirror/lint,
/autocomplete and /search, so they landed in the critical codemirror
chunk regardless of the runtime basicSetup flags. Since lint and
autocomplete are already loaded on demand via useEditorExtras, they were
being shipped twice — once eager, once deferred.

Replace the @uiw wrapper with a thin EditorView binding (controlled
value with an ExternalChange annotation to avoid onChange echo, plus
compartments for theme / base options / deferred extras) and hand-pick
only the primitives the critical path needs: state, view (lineNumbers,
highlightActiveLine, drawSelection, keymap), commands (history), and
language (syntaxHighlighting, indentOnInput, bracketMatching). lint,
autocomplete and the completions table stay in useEditorExtras.

Critical codemirror chunk 448KB/147KB gz -> 361KB/118KB gz. The
autocomplete + lint code moves to deferred chunks, off first paint.

Behaviour verified in a headless browser: typing/onChange, undo/redo,
auto-close tags, deferred autocomplete (65 options), XML lint markers,
and light/dark theme switch (oneDark applied) all work; no page errors.

LCP/FCP/CLS unchanged (already gated by the static skeleton since the
prior commit). Time-to-interactive-editor on throttled mobile improves
from 2111ms to 1821ms median, with far tighter run-to-run variance.

Drops the @uiw/react-codemirror dependency; promotes the four
@codemirror/* primitives to direct dependencies. @monaco-editor/react
stays (only referenced by a test mock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-21 15:49:37 +00:00
claude-code f916372572 Perf: paint the LCP element at FCP (2.0s -> 0.6s)
After seeding the result pane, LCP was still gated on React mounting:
the largest element stayed a CodeMirror line, which cannot exist until
the bundle has downloaded, parsed and rendered.

Fill the two skeleton panes with the welcome example's actual XSLT and
XML as plain <pre> text. A CSS gradient is not an LCP candidate but text
is, so the largest element on the page now paints with the static HTML
and nothing rendered later exceeds it.

The panes keep their existing box model (border-box holds them at the
same width; only intrinsic height grows with the content), so the
skeleton-to-app transition is unchanged. Measured CLS is identical.
The <pre> blocks are aria-hidden so the shell's aria-live region does
not announce a wall of markup.

Measured over 3 runs, mobile emulation, 4x CPU throttle, 1.6Mbps:
LCP 1988ms -> 624ms median, now equal to FCP. CLS 0.036 unchanged.
Cumulative over both commits: LCP 4064ms -> 624ms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-20 16:09:37 +00:00
claude-code 1773a0c0ef CI: use the built-in GITHUB_TOKEN instead of a long-lived PAT
The GH_TOKEN secret expired, breaking both workflows: ghcr.io login
failed with "denied: denied" and the Helm chart push failed with
"Authentication failed".

Both jobs only act on this repository, so the ephemeral GITHUB_TOKEN
covers them. publish.yml already declares packages: write and
publish-helm.yml already declares contents: write. Nothing left to
rotate, and no broadly-scoped PAT sitting in Actions secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-20 14:49:30 +00:00
claude-code 01a4715efc Perf: halve LCP (4.1s -> 2.0s) + surface Saxon vs round-trip timing
LCP was gated on the backend: the largest element on the page is a
CodeMirror line in the *result* pane, which could not exist until the
2s debounce plus the POST /transform round-trip had completed.

- Seed the welcome example's known Saxon output into the result pane on
  first visit so it paints on mount. The real transform still runs and
  overwrites it; duration/serverMs stay null so no timing is claimed
  until the real run lands.
- Drop the static xml-formatter import that was silently defeating the
  existing dynamic import (Vite warned about this).
- Load lint, autocompletion, hover docs and the ~50KB completions table
  after mount via requestIdleCallback; the editor reconfigures when they
  arrive and degrades gracefully if they fail.

Critical index chunk 100KB -> 56KB (28KB -> 17.7KB gzipped);
xsltCompletions split into its own deferred 39.7KB chunk.

Measured over 3 runs, mobile emulation, 4x CPU throttle, 1.6Mbps:
LCP 4064ms -> 2032ms median, CLS 0.033 -> 0.036, FCP unchanged.

Also includes the in-progress timing work: the success box now shows the
client round-trip alongside server-side Saxon time, and Saxon
compilation failures without a code prefix classify as "stylesheet".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDk5guu51FjFxQMjgrrekW
2026-07-20 13:55:51 +00:00
claude-code 22c5209939 Backend: structured error logging with one-click repro URLs + Grafana dashboard
Every failed transformation now emits a single JSON line to stdout
("event":"transform_error") that promtail already ships to Loki:

- class: input_xml (user data) / stylesheet (user XSLT) / backend (bug
  candidates) — lets review focus on real defects
- error_code extracted from the Saxon message (XPST0017, FORX0002…)
- xslt_b64 / params_b64: full payloads (capped at 48KB/field)
- repro_url: share-format URL (?xslt=&xml=&version=) — one click opens the
  playground with the exact failing case
- New "XSLT Playground — Errors" Grafana dashboard (errors by code, by
  class, recent log lines) added to the chart's dashboard ConfigMap

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-07-03 08:36:04 +00:00
claude-code b666f1116c SEO phase 2: Saxon error reference (17 pages) + 2 answer-first posts
Devs search literal error messages ("XPST0017", "Content is not allowed in
prolog") with almost no competition. New /xslt/errors/ section mirrors the
function-reference pattern that already ranks:

- 17 error pages: XPST0017/0081/0008, XPTY0004, XPDY0002, XTSE0630/0010,
  XTDE1490, XTMM9000, XTRE0540, FORX0002, FODC0002, FORG0001, SXXP0003 and
  the three big XML parser messages — each with meaning, causes and
  before/after fixes
- Pillar post "XSLT error messages explained" (triage table by error-code
  prefix, links all 17)
- Post "XSLT vs DataWeave" targeting the enterprise/MuleSoft audience
- Cross-links: functions hub ↔ errors hub; FirstUpper for category headings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-07-02 21:45:18 +00:00
claude-code e24ab01956 SEO: richer titles for 230 reference pages, categorized hub, cross-linking
Function pages ranked at pos 7-9 with 1.2% CTR (6.2k impressions/90d) and
the version pages sat at pos 22-24 with almost no internal links.

- Function/element pages: SERP title now "name — XPath function: syntax &
  examples" (one layout change, applies to all 230 pages)
- Reference hub: own title, intro copy, category sections with anchor nav
  (was a flat 230-item list ranking at pos 29.7)
- Cross-linking: version badge on every reference page links to its
  /xslt-2-0/ / /xslt-3-0/ page (~160 new internal links); version pages
  link back to the hub + 6 popular entries each; homepage links the hub

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-07-02 21:06:01 +00:00
claude-code d42d22e168 UX: reduce dead clicks on result pane and workspace title
Clarity heatmaps showed heavy dead clicks on the (blank) result pane and on
the "edit me!" workspace title.

- Result: add a one-click Copy button and an empty state ("Run a transform —
  the output appears here.") instead of a blank read-only editor
- Title: clicking the active workspace name now starts renaming (matches the
  "edit me!" hint); double-click also works

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-07-01 12:01:01 +00:00
claude-code 1692ae4360 Fix: source XML with &amp; entities corrupted by html.UnescapeString
pickSourceXML always ran html.UnescapeString on the chosen source document,
turning valid "&amp;" into a bare "&" and breaking XML parsing across all
engines (e.g. the default welcome example failed with "The entity name must
immediately follow the '&'"). The same corruption was already fixed for the
parameters path in e2fdbfd but the source path was missed.

Only unescape when the value is HTML-encoded (starts with "&lt;"); leave
already well-formed XML untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-06-30 14:54:25 +00:00
claude-code 71a018b5ac Fix duplicate xsl:param for primary input (regression from input redesign)
syncParams re-derived param names from the injected stylesheet but only
deduped against params[1..], so the primary input's injected name was added
again as a named param — producing two <xsl:param name="input"> and a Saxon
"Variable is multiply defined" error on the default workspace.

- Skip the primary input's name when syncing named parameters
- Filter out named params colliding with the primary (self-heals state
  persisted by the previous build)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-06-30 14:38:12 +00:00
claude-code ed4208c2fa Redesign input UX: first-class "Input XML" editor, named params secondary
User feedback asked to paste/edit input XML directly. The input was modeled
as named "parameters" in a "Data Pipeline" — each collapsed and requiring a
name, burying the common single-input case.

- Primary "Input XML" (params[0]) is now an always-visible, name-free editor
- Multiple named parameters move to a collapsible secondary "Named parameters"
  section; backend pickSourceXML already auto-detects the source document
- Add-param now opens + auto-names; removeParam guards the primary input
- Maintain params[0] invariant across defaults, clear and import
- Update DataPipelineHeader (title "Input") and its test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-06-30 14:06:36 +00:00
claude-code 1071d8de2c Add XSLT 3.0 tester comparison post, enrich README, ease ad refresh to 60s
- blog: "Best free online XSLT 3.0 testers compared (2026)" — answer-first
  comparison post to improve LLM/AI-engine and search visibility
- README: keyword-rich header with live tool link, Saxon HE 3.0
  differentiator, feature list and suggested GitHub topics
- frontend: raise EthicalAds auto-refresh throttle from 45s to 60s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgybX3QbAWVzb9ZnroCA79
2026-06-30 10:59:14 +00:00
alexandrev-tibco 675e460eee Metrics: place Grafana dashboard ConfigMap in observability namespace
The kube-prometheus-stack dashboard sidecar only watches its own namespace,
so the ConfigMap must live in observability to be auto-provisioned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 16:43:05 +02:00
alexandrev-tibco d1385899c4 Metrics: add component label to services so ServiceMonitor selects them
The ServiceMonitor selector matched on component=backend/frontend, but that
label only existed on the Service spec.selector, not metadata.labels, so the
prometheus-operator keep relabel dropped all targets. Add it to metadata.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 15:46:07 +02:00
alexandrev-tibco 2b2a16ed43 Metrics: add Prometheus instrumentation + Grafana dashboard
Instrument the Go backend with Prometheus metrics (HTTP request rate/
latency/in-flight and per-version transformation outcomes, Saxon duration,
payload size, trace usage), exposed on a dedicated internal port 9100 so
they are never routed through the public ingress. Add an nginx exporter
sidecar to the frontend. Ship ServiceMonitors (release=kube-prom-stack)
and a Grafana dashboard ConfigMap via the Helm chart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 15:28:46 +02:00
alexandrev-tibco 085f210f08 Ads: stop refreshing EthicalAds on backgrounded tabs
maybeRefreshAd only checked a stale adVisibleRef that stays true when the
tab is hidden, so the 30s timer kept firing reload() on background tabs —
generating ad decisions that never count as views and tanking the view
rate of the xsltplayground-main placement. Gate on document.visibilityState
and relax the interval to 45s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 22:23:50 +02:00
alexandrev-tibco f58cb158e0 SEO: target striking-distance keywords + strengthen landing pages and blog
Phase 1 (home): add explicit "viewer", "validate xslt online" and
"xslt transformation online" coverage in copy, meta keywords, a new
section and a FAQ entry (+ JSON-LD) to lift queries stuck at position 8-10.

Phase 2 (landing pages): add unique runnable examples with input/output
(for-each-group on /xslt-2-0/, maps + fold-left on /xslt-3-0/) plus
internal links to related blog posts and pre/code styling.

Phase 3 (blog): xsl-online-tester gets links to both version landing pages,
a FAQ block and related guides; xslt-string-functions gets a snippet-bait
summary table and related links.

Adds scripts/seo_report.py to pull GSC + GA4 metrics for tracking progress.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 17:19:37 +02:00
alexandrev-tibco e2fdbfd296 Fix: html.UnescapeString corrupts XML params with &amp; entities
When an XML parameter value starts with '<', html.UnescapeString was
incorrectly called, converting &amp; → & and breaking well-formed
XML that contained entity references. Only apply HTML decoding when
the value starts with '&lt;' (HTML-encoded XML sent from a form field).

Fixes: 'The entity name must immediately follow the & in entity reference'
error reported when XML input contains & characters escaped as &amp;.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:42:51 +02:00
alexandrev-tibco d08c525949 Set xsl:result-document article date to today (2026-05-16) 2026-05-28 16:27:38 +02:00
alexandrev-tibco 25bb1dce07 Add xsl:result-document support and blog article
- SaxonDaemon (Saxon 12): capture secondary outputs via setResultDocumentHandler
- Saxon2Daemon (Saxon 9.6): capture secondary outputs via OutputURIResolver
- Go backend: propagate secondaryResults field in TransformResponse
- Frontend: secondary results panel with collapsible items and copy button
- Blog: xslt-result-document.md covering basic syntax, split-by-category,
  multi-format output, serialization, XSLT 3.0 streaming, and limitations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:23:42 +02:00
alexandrev-tibco 7fa51fa4b7 Fix blog mobile LCP: only load adsbygoogle.js when inArticleSlot is set
adsbygoogle.js (28KB) + consent framework (126KB + 32KB) were loading
on every blog page despite inArticleSlot being empty — no ads shown,
186KB of JS downloaded for nothing. Mobile LCP: 11.1s → expected ~2-3s.

Keep the google-adsense-account meta tag for account verification.
Script now conditional on both client AND slot being configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:54:39 +02:00
alexandrev-tibco cc2360118c Remove BuyMeACoffee widget — 0 conversions, wrong audience
XSLT is a professional/enterprise tool. Donation models don't
convert for this audience. Removes the external CDN request and
floating button clutter. EthicalAds remains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 07:47:30 +02:00
alexandrev-tibco cb2dd59fac CTR optimization, sitemap lastmod, internal links to landing pages
1. Title: "XSLT Tester & Validator Online — Free, XSLT 1.0/2.0/3.0"
   Description: leads with "Run, test and validate" action verbs.
   Targets low-CTR queries: xslt tester, xsl validator (pos 6-8, CTR <1%).

2. Sitemap: add lastmod 2026-05-26 to all entries to signal freshness
   and accelerate Googlebot crawl of /xslt-2-0/ and /xslt-3-0/
   (both 0 impressions — never crawled despite being live for 3 weeks).

3. Blog internal links: update 3 posts to link directly to
   /xslt-2-0/ and /xslt-3-0/ landing pages instead of root,
   giving Googlebot anchor text and link equity for those URLs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:22:18 +02:00
alexandrev-tibco e1effdef1b Reduce bounce rate: blog CTA + welcome example with auto-run
A) Blog: add prominent playground-cta card at the end of each post.
   Styled with brand gradient border, eyebrow + headline + CTA button.
   Remove the buried related-cta line from related-posts.html.

B) App: load a meaningful XSLT welcome example for first-time visitors
   (no localStorage) and auto-run it immediately (autoRunReady=true),
   so new organic users see live output instead of a blank editor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:53:24 +02:00
alexandrev-tibco ac409af741 Fix Saxon2Daemon compilation against Saxon 9.6
Saxon 9.6 lacks ErrorReporter/XmlProcessingError (introduced in Saxon 10),
so Saxon2Daemon is now self-contained using JAXP ErrorListener. Runner.java
is excluded from the Saxon 9.6 build step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:00:06 +02:00
alexandrev-tibco e78b71cdbb Use Saxon HE 9.6.0.7 for XSLT 2.0 daemon (last true XSLT 2.0 processor)
Saxon 9.8+ supports XSLT 3.0, so Saxon 10 had the same validation gap
as Saxon 12. Saxon 9.6 is the last release focused on XSLT 2.0; it does
not process xsl:mode or other XSLT 3.0 declarations, making it a proper
XSLT 2.0 validator. Downloaded from SourceForge at build time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 12:55:08 +02:00
alexandrev-tibco 7733fcfc6d Fix: use Saxon HE 10.9 for XSLT 2.0 daemon (9.9.x not on Maven Central)
Saxon 9.9.x is not available on Maven Central; Saxon 10.9 is the closest
alternative (last pre-12 major release, stricter 2.0 semantics than 12).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 13:25:22 +02:00
alexandrev-tibco 6501d44580 Add preconnect hints for Clarity and EthicalAds origins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:32:21 +02:00
alexandrev-tibco 49c9281910 Add per-version XSLT engines: XSLTC (1.0), Saxon 9 (2.0), Saxon 12 (3.0)
Each XSLT version now runs against its own true processor rather than
everything going through Saxon 12 in backwards-compatible mode:
- version=1.0 → port 8082, JDK built-in XSLTC (proper XSLT 1.0 validator)
- version=2.0 → port 8083, Saxon HE 9.9.1.8 (true XSLT 2.0 semantics)
- version=3.0 → port 8081, Saxon HE 12.5 (unchanged)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:26:50 +02:00
alexandrev-tibco 2bd3e94bb9 Support xsl:initial-template when no source XML is provided; drop "only" from copy
- Backend no longer defaults to <root/> as source document; when no XML
  parameter is detected, source is empty and setInitialContextNode is
  skipped, allowing Saxon to invoke xsl:initial-template automatically
- Remove "The only free online XSLT" wording from meta descriptions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 20:21:34 +02:00
alexandrev-tibco 1921079c8a Fix Clarity script: missing 'script' argument in IIFE call
The snippet was called with 4 args instead of 5, causing r=clarityId
(createElement with wrong tag) and i=undefined (tag src broken).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 00:10:54 +02:00
alexandrev-tibco b56564f6f4 Add /xslt-2-0/ and /xslt-3-0/ internal links in footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:49:22 +02:00
alexandrev-tibco 5581258bd0 Handle ?v= URL param to pre-select XSLT version from landing pages
Reading /?v=2.0 or /?v=3.0 now sets the version on the active workspace
and updates the stylesheet version declaration. URL param is cleaned after
reading. Enables CTAs from /xslt-2-0/ and /xslt-3-0/ landing pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 13:17:24 +02:00
alexandrev-tibco 77a2d2c123 Restore dual-placement: fixedheader explicit load + stickybox via MutationObserver
Revert to pre-5d81b58 pattern that had both working:
- xsltplayground-main: fixedheader, always in DOM, loaded with load(element)
- xsltplayground-params: stickybox, inside params panel, only rendered when
  ethicalAdsReady=true so EthicalAds MutationObserver auto-discovers it
No createPortal, no conflict between explicit and auto-discovery loads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:05:00 +02:00
alexandrev-tibco b9a9ba2a93 Re-add fixedheader: load stickybox explicitly, header with delay
Load stickybox via load(element) first (proven to work), then load
fixedheader separately 300ms later to avoid conflict between the two calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:48:00 +02:00
alexandrev-tibco 406f55865c Restore stickybox to exact b5736c5 implementation
Remove fixedheader slot, restore single xsltplayground-params stickybox
via createPortal with load(element) + innerHTML clear, exactly as at b5736c5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:35:41 +02:00
alexandrev-tibco ace77fee33 SEO: rewrite homepage, add XSLT 2.0/3.0 landing pages, update sitemap
- Rewrite index.html title, meta description, H1 targeting "xslt online"
- Add static 600-word SEO section (crawlable by Googlebot, outside #root)
- Add FAQPage + BreadcrumbList + SoftwareApplication schema.org markup
- New /xslt-2-0/ landing page (~800 words, feature grid, FAQ)
- New /xslt-3-0/ landing page (~900 words, "only free tool" badge, FAQ)
- Update sitemap.xml to include all three URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 16:42:08 +02:00
alexandrev-tibco 986a1e05ee Add robots.txt — was returning SPA index.html (404 for crawlers)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:58:17 +02:00
alexandrev-tibco 23a7f75e56 Restore stickybox: createPortal to body + original placement ID
Original working implementation used createPortal to document.body and
id=xsltplayground-params. Restore both to match the known-working state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:39:27 +02:00
alexandrev-tibco c4f78816bf Use ethicalads.load() no-arg to discover both slots simultaneously
Calling load(element) twice in sequence was silently failing on the second.
No-arg load() auto-discovers all EA elements in the DOM at once.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:25:12 +02:00
alexandrev-tibco 72306d2215 Fix stickybox: always in DOM, loaded alongside fixedheader on EA ready
Both slots rendered unconditionally at app root — EA's stickybox is
position:fixed so it doesn't need to live inside the result pane.
Single effect loads both refs when ethicalAdsReady.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:14:19 +02:00
alexandrev-tibco 5e7b1c0764 Reserve 50px for fixedheader slot to prevent layout shift on load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:31:45 +02:00
alexandrev-tibco a480a05e03 Fix stickybox not loading: separate effect triggered by showResultPane
The stickybox slot only mounts after the first transform (showResultPane=true).
Split into two effects: header loads on ethicalAdsReady, sticky loads when
showResultPane changes and its ref is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:28:08 +02:00
alexandrev-tibco 9a1b9e7108 Restore fixedheader + add stickybox in result pane
- fixedheader: native EthicalAds placement at top of app (above tabs),
  full-width sticky bar as designed
- stickybox: new slot in result pane right column (160px), shown whenever
  result pane is visible
- Two slots loaded via separate refs; both initialized on ethicalAdsReady

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:21:52 +02:00
alexandrev-tibco 2f525dacee Add AdSense verification meta tag to frontend and blog
Adds google-adsense-account meta for publisher ca-pub-1549720748100858
to both xsltplayground.com and blog.xsltplayground.com.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:32:18 +02:00
alexandrev-tibco 2ee596888a Use fixedheader style natively, neutralize position:fixed only
Let EthicalAds render its own horizontal bar layout via data-ea-style=fixedheader.
CSS only overrides position/z-index to keep it inline instead of viewport-fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 13:12:23 +02:00
alexandrev-tibco 70da86d7d6 Force ea-callout inline: flex-row on both ethical-ad and ea-content
Apply flex-direction:row + inline-flex on callout regardless of which
nesting level EthicalAds places it at.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 13:01:14 +02:00