Product analytics
Analytics products know that a user reached checkout. They do not know that the button re-rendered fourteen times first, or that the request behind it took two seconds, because those live in a different tool. Here they are rows in the same database.
Three calls, all optional
The graph already records navigation, gestures, renders and requests without you doing anything. These three calls add the vocabulary only you have — what a thing is called in your product, and who did it:
import { defineAction, tag, identify } from '@squiggle/react-native';// A named user action. The wrapper times each invocation to settlement and// records duration + ok/error — the feature-usage and funnel-step signal.const addToCart = defineAction('add_to_cart', () => cart.getState().add(product));addToCart(); // invoke the wrapper, not the original// Key-value enrichment. Inherits the live interaction chain, so a tag inside a// tap → render chain enriches that interaction.tag({ experiment: 'checkout-v2', variant: 'A' });// Identity. Sets the session's user and shallow-merges traits. Safe to call// before a consumer connects — the latest identity is replayed when one does.identify('user-123', { plan: 'pro', appVersion: '1.4.2' });defineAction is the important one. It is not an event you fire and forget — it wraps the function, times it to settlement, and records whether it succeeded. That single decision is what lets one name serve as a funnel step, a feature-usage metric, a performance measurement and a flow assertion: assert: { action: { checkout.submit: { eq: 1 } } } is the strongest proof available that a journey actually did the thing, rather than that a tap was delivered.
Engagement
- Screens
- Per-route visits, average time, and exit percentage, with a collapsible top-paths list — the journeys people actually take through the app rather than the ones the navigator allows.
- Feature usage, and adoption
- Per-
defineActioncounts with error badges — and a toggle from raw count to adoption, the penetration of active sessions. These rank differently and the difference is the point: a feature used ten times by one person is not the feature used once by everyone. - Feature affinity
- Co-occurrence pairs ranked by lift — “users who use X also use Y”. Useful for deciding what to put next to what.
- Friction
- The cross-session rollup of the interaction detectors — rage taps, dead zones, confusion — grouped by screen with a ranked worst-spots list. Each spot drills into the single session where it happened. This is the join no other stack can do: a UX defect detected from runtime causality, aggregated as a product metric.
Funnels
A funnel is an ordered list of steps, where a step is a screen or an action. Build it in the desktop with observed-value suggestions, or define it headlessly — either way it lands under the funnels key of .squiggle/perf/config.json, beside the store it is evaluated against, so the CLI, the desktop and an agent read exactly the same definition.
Defining and running one
squiggle product funnel list # what is definedsquiggle product funnel save checkout # write a definition to the sidecarsquiggle product funnel run checkout # evaluate it, as an ASCII chartsquiggle product funnel rm checkout # remove the definitionBecause a step names a screen or an action rather than a coordinate, a funnel keeps working when the route is redesigned. That is the same property that makes a flow selector durable, for the same reason.
What it reports
- Per-step reach and conversion
- The chart plus drop-off lines between steps.
- Time to convert
- Median and p90 — because a funnel that converts at 80% in four seconds and one that converts at 80% in four minutes are different products.
- Trait segmentation
- Split by any
identify()trait: plan, experiment variant, app version. One report per distinct value. - Multi-path steps
- A step can accept several names as an OR, for a journey with more than one legitimate route through it.
- Optional time window
- Bound how long a conversion may take to count.
Audience
- Retention
- A cohort matrix — first-seen cohorts against return offsets, day or week. Cells for periods that have not elapsed yet are marked as such rather than shown as zero, which is the difference between “they did not come back” and “it is not tomorrow yet”.
- Releases
- Version annotations derived from the
identify()release trait, with first-seen date and session count — so a metric change can be lined up against a ship. - Users
- Identified users with their current traits, session counts, total time, last seen, and a churn-risk flag.
- Sessions
- The session table — duration, user, screens, actions. Each row expands into an event ribbon (navigations, actions, gestures and errors on one time axis) and deep-links into the Performance Center for that session.
Why it is in the same tool
Consider the question “which release made checkout slow, and for whom?”. Normally that spans three products: a release tracker, an APM, and an analytics tool, with no shared key between them. Here it is one query — releases come from a trait, checkout timing comes from the defineAction duration, and the segment comes from the same traits — over one SQLite file.
Or: “users who drop out of this funnel — what were they doing instead?” The friction rollup already knows, because the same session that failed to convert also has its rage taps recorded against a named control.
From the terminal
squiggle product overview # headline KPIssquiggle product screens # per-route visits, time, exit %squiggle product usage --adoption # feature penetration, not raw countssquiggle product affinity # "users who use X also use Y"squiggle product retention # cohort matrixsquiggle product friction --last 20 # where people are strugglingsquiggle product funnel run checkout # ASCII conversion funnelScoping a report
--session <id>- One run. What you want when investigating a specific failure.
--last N- A rolling window of the most recent N sessions — the default shape for “is this getting better or worse”.
--adoption·--weekly·--limit N- The three report-specific ones: adoption rates on
usage, weekly cohorts onretention, and a row cap onsessions. --json- The payload alone on stdout, every human line moved to stderr, so any of these is a pipeline stage.
Every one of these has a matching MCP tool, so an agent investigating a drop-off reads the same numbers you do — see Agents & MCP.