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
<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.
// 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 });# 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 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.
// 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:
<!-- 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
<softechlog-feed [attr.feed-token]="feedToken" theme="auto" ></softechlog-feed>
In React
<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.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
| Attribute | Type | Default | Description |
|---|---|---|---|
feed-token | string | required | Actor-scoped token from POST /v1/feed-tokens |
target-type | string | — | Only events on this resource type |
target-id | string | — | Only events on this resource |
theme | light · dark · auto | auto | auto follows prefers-color-scheme |
limit | number | 25 | Events per page (max 100) |
locale | string | browser | Locale for dates and times |
empty-text | string | No activity yet. | Copy for the empty state |
api-url | string | https://api.softechlog.com | Override 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:
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:
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.