AkurAI Build
Menu

Public repository / fee5b6c2cde89fa6bf8dc51068af7336b56a88b8

AkurAI-Framework / site/frontend/boundaries-demo.html

Latest change 5fdd8b81e1f8244cbfc64e15d9ca2d5dba604bfb - fix: ensure boundaries.js loads before demo code runs by Ólafur Búi Ólafsson

{% include "header" %}
    <main class="container">
      <header class="page-head">
        <span class="eyebrow">Live demo</span>
        <h1>Loading &amp; Error Boundaries</h1>
        <p class="lede">Watch the top progress bar as you navigate between pages. Below, <code>withBoundary()</code> wraps async data fetches in graceful loading and error UI — no network failures break the page.</p>
      </header>

      <!-- SPA Navigation Demo -->
      <section class="split">
        <div>
          <h2>Navigation Progress</h2>
          <p>Click any link below. The progress bar at the top of the page shows the SPA navigation in progress. If the fetch fails, it falls back to a full-page navigation, keeping the user unstranded.</p>
          <div class="btn-row" style="margin-top: 1.5rem;">
            <a class="btn btn-primary" href="/">Home</a>
            <a class="btn btn-secondary" href="/docs">Docs</a>
            <a class="btn btn-secondary" href="/components">Components</a>
            <a class="btn btn-ghost" href="/realtime">Realtime</a>
          </div>
        </div>
        <div style="background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem;">
          <h3 style="margin-top: 0;">Console breadcrumbs</h3>
          <p class="small muted">Open DevTools → Console to see <code>[boundaries]</code> log messages as you navigate:</p>
          <ul class="small muted" style="margin: 1rem 0 0; padding-left: 1.5rem;">
            <li><code>[boundaries] initialization</code></li>
            <li><code>[boundaries] navigation start</code></li>
            <li><code>[boundaries] navigation end</code></li>
          </ul>
        </div>
      </section>

      <!-- withBoundary Demo: Slow Fetch -->
      <section style="margin: 4rem 0;">
        <h2>Async Data with Loading Boundary</h2>
        <p>Click "Load data" below. The boundary shows a spinner while fetching, then renders the content.</p>
        <button class="btn btn-primary" id="demo-slow-load">Load data (slow)</button>
        <div id="demo-slow-content" style="margin-top: 1.5rem;"></div>
      </section>

      <!-- withBoundary Demo: Error Handling -->
      <section style="margin: 4rem 0;">
        <h2>Error Boundary with Retry</h2>
        <p>Click "Load and fail" below. The boundary catches the error, renders an error banner, and provides a Retry button.</p>
        <button class="btn btn-primary" id="demo-error-load">Load and fail</button>
        <div id="demo-error-content" style="margin-top: 1.5rem;"></div>
      </section>

      <!-- withBoundary Demo: Custom Loading (Skeleton) -->
      <section style="margin: 4rem 0 6rem;">
        <h2>Custom Loading UI</h2>
        <p>This boundary shows a skeleton placeholder instead of a spinner, demonstrating how to customize loading states.</p>
        <button class="btn btn-primary" id="demo-skeleton-load">Load with skeleton</button>
        <div id="demo-skeleton-content" style="margin-top: 1.5rem;"></div>
      </section>

      <section class="container cta">
        <h2>Ready to handle errors gracefully?</h2>
        <div class="btn-row center">
          <a class="btn btn-secondary" href="/docs/boundaries">Boundaries documentation</a>
          <a class="btn btn-secondary" href="https://github.com/olibuijr/AkurAI-Framework">Source on GitHub</a>
        </div>
      </section>
    </main>

    <!-- Load boundaries.js before any demo code -->
    <script type="module">
      // Import boundaries.js to ensure it initializes before our demo code runs.
      import "/boundaries.js" /*.then(() => { ... }) */;

      // Guard on document.readyState to ensure DOM + boundaries.js are ready.
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", initDemos);
      } else {
        initDemos();
      }

      function initDemos() {
        // Demo 1: Slow fetch
        var slowLoadBtn = document.getElementById("demo-slow-load");
        var slowContent = document.getElementById("demo-slow-content");
        if (slowLoadBtn && slowContent) {
          slowLoadBtn.addEventListener("click", function () {
            // Simulate a 2-second network delay.
            window.SpaNavBoundary.withBoundary(
              slowContent,
              function () {
                return new Promise(function (resolve) {
                  setTimeout(function () {
                    resolve(
                      '<div class="card">' +
                      "<strong>Data loaded successfully!</strong>" +
                      "<p>This content was fetched asynchronously and rendered inside the boundary. " +
                      "The spinner appeared while loading, then vanished once the promise resolved.</p>" +
                      "</div>"
                    );
                  }, 2000);
                });
              },
              { loading: true, error: true }
            );
          });
        }

        // Demo 2: Error + Retry
        var errorLoadBtn = document.getElementById("demo-error-load");
        var errorContent = document.getElementById("demo-error-content");
        if (errorLoadBtn && errorContent) {
          errorLoadBtn.addEventListener("click", function () {
            window.SpaNavBoundary.withBoundary(
              errorContent,
              function () {
                return Promise.reject(new Error("Network request failed: 503 Service Unavailable"));
              },
              { loading: true, error: true }
            );
          });
        }

        // Demo 3: Skeleton loading
        var skeletonLoadBtn = document.getElementById("demo-skeleton-load");
        var skeletonContent = document.getElementById("demo-skeleton-content");
        if (skeletonLoadBtn && skeletonContent) {
          skeletonLoadBtn.addEventListener("click", function () {
            // Custom loading UI: show a skeleton card instead of a spinner.
            skeletonContent.innerHTML =
              '<div class="skeleton skeleton-card"></div>' +
              '<div class="skeleton-line" style="margin-top: 1rem;"></div>' +
              '<div class="skeleton-line skeleton-line-sm"></div>';
            skeletonContent.setAttribute("aria-busy", "true");

            // Fetch data after a delay.
            Promise.resolve()
              .then(function () {
                return new Promise(function (resolve) {
                  setTimeout(function () {
                    resolve(
                      '<div class="card">' +
                      "<strong>Skeleton replaced!</strong>" +
                      "<p>The skeleton loader was replaced with real content. " +
                      "You can customize loading UI per boundary by pre-populating the container.</p>" +
                      "</div>"
                    );
                  }, 1500);
                });
              })
              .then(function (html) {
                skeletonContent.innerHTML = html;
                skeletonContent.removeAttribute("aria-busy");
                console.log("[boundaries] skeleton content rendered");
              })
              .catch(function (err) {
                skeletonContent.innerHTML =
                  '<div class="banner banner-danger">' +
                  '<span class="banner-icon">✕</span>' +
                  '<div class="banner-text"><strong>Failed</strong>' +
                  "<p style='margin:0.5rem 0 0;'>" +
                  err.message +
                  "</p></div>" +
                  "</div>";
                skeletonContent.removeAttribute("aria-busy");
              });
          });
        }

        console.log("[boundaries-demo] initialization complete");
      }
    </script>

    <!-- Load app.js last (after boundaries module import above) -->
    <script type="module" src="/app.js"></script>
{% include "footer" %}