Files
member-console/internal/embeds
cgalo5758 a72a16b3dd Name the recent-activity page through the request's resolver
loadRecentActivity takes the page's ListParams, reads, pairs, builds and sorts
as before, cuts the page with pageSliceClamped and names only the page's rows.
A new pageNames asks the operator names resolver for the page's organizations,
people, products and ladders, so each kind is one batch read however many
rows the page holds. feedNames wraps the resolver for the row builder: a name
the batch did not return is empty, and a tier is labelled by tierLabel, so
the per-row readers perRowNames, tierNameResolver and newTierNameResolver are
deleted. renderLanding and wholeFeed call the new signature, and the
comments on ActivityNav, renderLanding and the landing template say the
loader windows and names the page.

No output changes: feed.json and feed.html are byte-identical, the 1406 test
and subtest names of internal/server have the same results at the parent and
here apart from the new TestOperatorPagesReadAFixedNumberOfTimes/landing,
the operator walkthroughs (22, 16 pass, 6 skip) are equal at both commits, and
the screens differ only in operator-integration-stripe (both widths) and the
Age cell of operator-domains, which differ on every run. The goldens and
nets from earlier commits did not change.

TestRecentActivityReadsPerEvent expects no difference between one and three
grows: every statement count is the same, where the four per-row reads grew by
two. TestRecentActivityReadFailures fails the batch reads instead of the
per-row ones (ListProductNamesByIDs, ListTiersWithProductsByLadderIDs,
ListOrganizationNamesByIDs, ListPersonNamesByIDs), with the same names and
assertions. That is NARROWER in one way: a read that fails for one id or one
ladder alone can no longer happen, and a failed batch blanks every name of its
kind on the page, which is what each row showed when its own read failed.
TestOperatorPagesReadAFixedNumberOfTimes gains a landing row on a scratch
database, with no per-row statements: after three grows the feed holds 6
events, fewer than its page of 10, so a read per named row would show.

Breaks run at the parent (8 records) and here (15), all caught: the product,
tier, organization and person names, the landing's page and count, the blank
name of a failed product, organization, person and tier read, and each Want
call the page makes. Not probed: that only the page's rows are named and not
the whole window. The resolver reads one batch per kind either way and the
page's names are the same, so neither the output nor the statement count tells
the page from the window. Also not probed here: that an empty id is skipped
instead of read, which the resolver's own tests cover.
2026-10-10 06:03:14 -05:00
..
2026-09-06 02:29:42 -05:00

Embedded Assets

This package provides embedded static files, templates, and configuration using Go's embed directive.

Overview

All assets are embedded at compile time, producing a single binary with no external file dependencies.

package embeds

//go:embed static
var Static embed.FS

//go:embed templates
var Templates embed.FS

//go:embed mc-config.yaml
var Config embed.FS

Directory Structure

internal/embeds/
├── embeds.go           # Embed directives
├── mc-config.yaml      # Default configuration template
├── static/             # Static assets (CSS, JS, images)
│   ├── bootstrap.css
│   ├── bootstrap.bundle.js
│   ├── htmx.min.js
│   └── favicon*.png
└── templates/          # HTML templates
    ├── index.html      # Main page template
    └── partials/       # HTMX partial templates
        ├── fedwiki_sites.html
        ├── fedwiki_create_form.html
        ├── fedwiki_delete_confirm.html
        └── fedwiki_delete_success.html

Static Assets

Serving Static Files

staticFS, _ := fs.Sub(embeds.Static, "static")
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))

Included Libraries

  • bootstrap.css (5.3.x) - CSS framework
  • bootstrap.bundle.js (5.3.x) - Bootstrap JS (includes Popper)
  • htmx.min.js (2.0.4) - HTMX for dynamic content

Favicons

Standard favicon set for various platforms:

  • favicon.ico - Traditional favicon
  • favicon-16x16.png, favicon-32x32.png - Standard sizes
  • apple-touch-icon.png - iOS home screen
  • android-chrome-*.png - Android home screen
  • site.webmanifest - PWA manifest

Templates

Main Page Template (index.html)

The full HTML page that serves as the application shell:

<!DOCTYPE html>
<html>
<head>
    <!-- Meta, CSS, HTMX -->
</head>
<body>
    <!-- Navigation -->
    
    <!-- Content sections with HTMX triggers -->
    <div hx-get="/partials/fedwiki/sites" 
         hx-trigger="load, refreshSites from:body">
        <!-- Loading spinner -->
    </div>
    
    <!-- Modals -->
    <div class="modal" id="createSiteModal">...</div>
    
    <!-- Scripts -->
</body>
</html>

Partial Templates (partials/)

HTML fragments returned by HTMX handlers. These are not full pages.

Naming Convention

{feature}_{action}.html

Examples:

  • fedwiki_sites.html - Sites list display
  • fedwiki_create_form.html - Site creation form
  • fedwiki_delete_confirm.html - Delete confirmation dialog
  • fedwiki_delete_success.html - Deletion success message

Template Data Pattern

Each partial has a corresponding Go struct for its data:

// In fedwiki_partials.go
type SitesData struct {
    Sites        []SiteViewModel
    CurrentCount int64
    Quota        int64
    IsMember     bool
    CanCreate    bool
    Error        string
}

HTMX Patterns in Templates

Loading Content on Page Load

<div hx-get="/partials/fedwiki/sites" 
     hx-trigger="load, refreshSites from:body"
     hx-swap="innerHTML">
    <!-- Initial loading spinner -->
    <div class="spinner-border"></div>
</div>

Form Submission

<form hx-post="/partials/fedwiki/sites" 
      hx-swap="innerHTML" 
      hx-target="#createSiteModalBody">
    <!-- Form fields -->
    <button type="submit">
        <span class="spinner-border d-none" id="spinner"></span>
        Create Site
    </button>
</form>

Loading Button into Modal

<button data-bs-toggle="modal" 
        data-bs-target="#createSiteModal"
        hx-get="/partials/fedwiki/create-form" 
        hx-target="#createSiteModalBody">
    Create Site
</button>

Conditional Button State

<button {{ if .CanCreate }}
          hx-get="/partials/fedwiki/create-form"
        {{ else }}
          disabled title="Site limit reached"
        {{ end }}>
    Create Site
</button>

Spinner Pattern

Spinners are hidden by default and shown during HTMX requests:

<!-- CSS to show spinner during request -->
<style>.htmx-request #createSpinner { display: inline-block !important; }</style>

<!-- Spinner hidden by default -->
<span class="spinner-border d-none" id="createSpinner"></span>

The htmx-request class is automatically added to elements during requests.

Configuration Template

mc-config.yaml provides a default configuration template that can be extracted:

// Extract default config
configData, _ := embeds.Config.ReadFile("mc-config.yaml")

This is used by the init command to create a starter configuration file.

Usage

Accessing Templates

import "git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"

// For full page templates
tmpl, err := template.ParseFS(embeds.Templates, "templates/index.html")

// For partial templates
partialFS, _ := fs.Sub(embeds.Templates, "templates/partials")
tmpl, err := template.ParseFS(partialFS, "*.html")

Accessing Static Files

// Serve static files
staticFS, _ := fs.Sub(embeds.Static, "static")
http.Handle("/static/", http.FileServer(http.FS(staticFS)))