Docs

Feed Component

A drop-in Web Component that renders clean, paginated activity feeds anywhere in your product — account settings, team pages, admin views. Built with Angular Elements (zoneless, Shadow DOM), framework-agnostic, ~42 kB gzipped.

Load the script

index.htmlfeed.js
<script src="https://softechlog.com/feed.js"></script>

Mint a feed token on your server

Public keys are ingest-only, so the browser cannot read activity with one. Instead your backend mints a feed token (stl_ft_…) with the secret key: a short-lived, read-only credential pinned to one actor, one tenant (your customer's account), or both — and optionally one target. Any page holding it can read only that slice of activity — nothing else, and never exports or stats.

api/activity-token.tsNode SDK
// GET /api/activity-token — @futuregensystems/softechlog
const { token, expires_at } = await log.feedToken({
  actorId: req.user.id, // whose activity may be read
  ttlSeconds: 3600,     // 60 – 86400 (default 3600)
  // targetType: "workspace", targetId: workspace.id  // optionally pin to one resource
});
res.json({ token, expires_at });
app/activity.pyPython SDK
# GET /activity-token — softechlog (Python)
@router.get("/activity-token")
async def activity_token(current_user: User = Depends(get_current_user)):
    tok = await log.afeed_token(actor_id=str(current_user.id), ttl_seconds=3600)
    return {"token": tok.token, "expires_at": tok.expires_at}
POST /v1/feed-tokensraw HTTP
POST https://api.softechlog.com/v1/feed-tokens
Authorization: Bearer stl_sk_xxxxxxxxxxxx
Content-Type: application/json

{ "actor_id": "user_123", "ttl_seconds": 3600 }

→ 201 { "token": "stl_ft_…", "expires_at": "2026-08-18T15:09:31Z", "actor_id": "user_123", "tenant_id": null }

Account-wide feeds (tenant tokens)

B2B customers usually ask for "who did what in our account", not just one person's history. Track events with a tenantId (your org / workspace id), then mint the token with tenantId instead of — or as well as — actorId. Decide on your server who may see the whole account (typically its admins); the token is the only thing that enforces the scope.

api/audit-token.tsNode SDK
// An account admin's audit trail: everything that happened in their org.
// Only events tracked with this tenantId appear — see /docs/manual-tracking.
if (!req.user.isAdmin) return res.status(403).end();
const { token } = await log.feedToken({ tenantId: req.user.orgId });

// One teammate within the org: pass both.
// await log.feedToken({ tenantId: req.user.orgId, actorId: member.id });

Tenant tokens read events only (/v1/actors and /v1/sessions answer 403). Auto-captured events never carry a tenant: public keys can't set one, so the account trail holds only what your server recorded.

Tokens live 60 seconds to 24 hours (ttl_seconds, default 3600). When one expires the component shows an "expired" error state — fetch a fresh token from your endpoint and set the attribute again.

Usage

Fetch the token from your backend when the page loads, then set the feed-token attribute. Add target-type / target-id to narrow the feed to one resource:

settings.htmlplain HTML
<!-- Everything the signed-in user did -->
<softechlog-feed id="feed"></softechlog-feed>

<script>
  fetch('/api/activity-token')
    .then((r) => r.json())
    .then(({ token }) => document.getElementById('feed').setAttribute('feed-token', token));
</script>

<!-- Only what they did to one resource -->
<softechlog-feed
  feed-token="stl_ft_…"
  target-type="workspace"
  target-id="ws_abc"
></softechlog-feed>

In Angular

settings.component.htmlAngular
<softechlog-feed
  [attr.feed-token]="feedToken"
  theme="auto"
></softechlog-feed>

In React

Settings.jsxReact
<softechlog-feed feed-token={token} theme="auto" />

In a TypeScript project (every default Next.js app) the custom element needs a one-time type declaration, or next build fails with Property 'softechlog-feed' does not exist on type 'JSX.IntrinsicElements':

softechlog-feed.d.tsTypeScript
// softechlog-feed.d.ts — teach TypeScript about the custom element
import type { DetailedHTMLProps, HTMLAttributes } from 'react';

type SoftechlogFeedProps = DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & {
  'feed-token'?: string; 'target-type'?: string; 'target-id'?: string;
  theme?: 'light' | 'dark' | 'auto'; limit?: number | string; locale?: string;
  'empty-text'?: string; 'api-url'?: string;
};

declare global {            // React 18
  namespace JSX { interface IntrinsicElements { 'softechlog-feed': SoftechlogFeedProps } }
}
declare module 'react' {    // React 19
  namespace JSX { interface IntrinsicElements { 'softechlog-feed': SoftechlogFeedProps } }
}

Attributes

AttributeTypeDefaultDescription
feed-tokenstringrequiredActor-scoped token from POST /v1/feed-tokens
target-typestring—Only events on this resource type
target-idstring—Only events on this resource
themelight · dark · autoautoauto follows prefers-color-scheme
limitnumber25Events per page (max 100)
localestringbrowserLocale for dates and times
empty-textstringNo activity yet.Copy for the empty state
api-urlstringhttps://api.softechlog.comOverride the API base URL

The token already fixes the actor, so there is no actor-id attribute to set — and no attribute can widen the token's scope.

Content-Security-Policy

If your site sets a CSP, allow the script and the API:

Content-Security-Policydirectives to add
script-src  https://softechlog.com;
connect-src https://api.softechlog.com;
img-src     https://your-avatar-cdn.example;   /* if you pass avatar_url */

Theming with CSS custom properties

The component renders in Shadow DOM, so your page CSS can't leak in — and its styles can't leak out. Match your design system through custom properties:

styles.cssCSS custom properties
softechlog-feed {
  --stl-accent: #ee6c2f;        /* avatar gradient & interactive colour */
  --stl-bg: #ffffff;            /* card background */
  --stl-text: #0e1a2c;          /* primary text */
  --stl-muted: #94a3b8;         /* secondary / meta text */
  --stl-border: #e2e8f0;        /* border & divider colour */
  --stl-border-radius: 0.75rem; /* outer card corner radius */
  --stl-avatar-size: 32px;      /* avatar circle diameter */
  --stl-font-family: 'Inter', system-ui, sans-serif;   /* or var(--your-font) */
}

Your values apply in both light and dark mode — with theme="auto" the built-in dark palette is only a fallback for tokens you haven't set.

Loading skeleton, "Load more" pagination, empty state, and human-readable errors (expired token, network, rate limit) are built in — you don't need to handle them yourself.