Pre-1.0. Not intended for public use yet. The API is subject to undocumented change, and these pages may already be wrong about it.

Skip to content

Aggregation ​

Introduction ​

Aggregation combines related activities into one feed item, so three orders from one customer appear as one row.

Grouping Activities ​

Repeated Activities ​

Here are three orders from one customer, minutes apart, in log mode:

Define a headline with grouped() to describe them together:

routes/feed.php
php
use App\Models\Order;
use Storyfeed\Facades\Story;
use Storyfeed\Grouping\GroupBuilder;

Story::for(Order::class)
    ->verb('place')
    ->grouped(
        fn (GroupBuilder $group) => $group
            ->repeat(':actor placed :count orders with :target'),
    );
ES
Erica Sinclair placed 3 orders with Scoops Ahoy

Activities Sharing a Role ​

Five customers ordering from the same shop need a different sentence. Each group names an axis: what its activities have in common.

routes/feed.php
php
use Storyfeed\Facades\Story;
use Storyfeed\Grouping\GroupBuilder;

Story::verb('place')->grouped(
    fn (GroupBuilder $group) => $group
        ->actors(':actors ordered from :target'),
);
MMLSMW
Max Mayfield, Lucas Sinclair, Mike Wheeler and 2 more ordered from Scoops Ahoy

A repeat group contains one type, so its headline belongs under Story::for(Order::class) and can say "orders". An actors group may also contain reservations, so its headline belongs on the verb alone and must not name a type.

Storyfeed groups activities when published. Each activity belongs to only one group per read mode. Quotes and images belong to the activities, so use log() to show each one. Storage Architecture shows where groups are stored and how a feed retrieves them.

Choosing a Read Mode ​

In a longer feed, repeated actions and activities at busy places appear as rows you can expand:

Today

DH
Dustin Henderson wrote Planck’s constant
Planck’s constant is 6.62607004.
Suzie, over Cerebro
RBSHES
MB
a hot dog
a hot dog
A
Alexei played a carnival game 3 times
JBJHA
Joyce Byers, Jim Hopper, Alexei and 4 more checked in at the Fun Fair
SH
Steve Harrington viewed 5 orders
KW
a bag of soft pretzels
a bag of soft pretzels
KW
Order #1042 items
2 moreOrder #1042
JT
Jasper Tey had a bite of a pretzel at the Fun Fair
a pretzel
a pretzel
LS
the fireworks
the fireworks
MMLSWB
Max Mayfield, Lucas Sinclair, Will Byers and 5 more checked in at the Hopper cabin
S
Stripe processed 3 payments
MB
Murray Bauman wrote Murray’s gate investigation
Murray’s gate investigation

What the machine needs

Alexei’s account: the machine is opening a gate beneath the mall.

  • Locate the control room.
  • Reach the two shutdown keys.
  • Check the safe combination before going in.

Working note: translation is evidence, not a complete floor plan.

Order #1040 expired at Scoops Ahoy

Yesterday

SR
Scoops Register cancelled Order #2071
RB
Find where the elevator goes

Find out where the elevator in the storeroom goes down to.

THBLJB
MB
Murray Bauman let the chief in at Murray's warehouse
N
Ned served Order #1036
SH
USS Butterscotch
Price
$2.95
Section
Sundaes
Available
At the counter
Orders
12
JB
Joyce Byers checked in at the Hess farm
JH
Jim Hopper checked in at the Hess farm

Tuesday

ES
Erica Sinclair completed 2 tasks on the Scoops Troop board
WBMMLS
ESMMLS
Erica Sinclair, Max Mayfield, Lucas Sinclair and 2 more ordered from Scoops Ahoy
S
Stripe marked Scoops Ahoy invoice #1985 paid
Scoops Ahoy invoice #1985
Amount
$497.00
Billed to
Starcourt Mall
Reference
INV-1985
S
Stripe marked Order #2031 paid
SH
Steve Harrington marked 2 orders ready
SH
Steve Harrington confirmed 4 orders
SH
Steve Harrington confirmed Order #1035 2 times
ESRBSH
S
Stripe marked Order #1035 paid 2 times
ES
Erica Sinclair replied about Is that free for life, or only for today?
SH
ES
ES
Erica Sinclair sent a note about Order #1035
ES
Erica Sinclair saved Order #2031 2 times
SH
Steve Harrington changed the price of USS Butterscotch
USS Butterscotch
Price
$2.95
Section
Sundaes
Available
At the counter
Orders
12
SH
Steve Harrington added a photo of USS Butterscotch
USS Butterscotch
USS Butterscotch
TH
SH
Steve Harrington put USS Butterscotch on the menu
USS Butterscotch
Price
$2.95
Section
Sundaes
Available
At the counter
Orders
12
JB
Joyce Byers checked in at Hawkins Town Hall
JH
Jim Hopper checked in at Hawkins Town Hall
SH
2 activities

Starcourt Mall visitor guide

The visitor guide includes entrances, parking and shop locations.

Scoops Ahoy opening hours

The counter opens at 10 am. Orders are available until 9 pm.

Monday

JH
Jim Hopper checked in at Hawkins Lab
JB
Joyce Byers checked in at Hawkins Lab
JB
A fairground at night
A fairground at night
JT
Jasper Tey merged 2 pull requests into cerebro
ESRBDH
RB
Map the code to the mall

Match each line of the message to a place in the mall.

SC
Ticket #101

Every magnet on the store display fell off at once, then did it again an hour later.

E

Sunday

SH
Match the music to the mall

Pick out the mall in the sounds behind the message on the tape.

RB
Crack the code

Work out what the Russian message on the tape is saying.

JH
Jim Hopper checked in at Enzo's
JH
Jim Hopper booked a table at Enzo's
S
Stripe marked Scoops Ahoy invoice #1984 paid
Scoops Ahoy invoice #1984
Amount
$520.75
Billed to
Starcourt Mall
Reference
INV-1984
JH
Jim Hopper checked in at Hawkins Town Hall
MM
Max Mayfield checked in at Starcourt Mall
E
Eleven checked in at Starcourt Mall
DH
Dustin Henderson checked in at Scoops Ahoy
JB
Jonathan Byers uploaded 4 photos to the Hawkins Post
Rolled newspapersA room lit redArcade machines in a dark room
TH
They came back every night for my fertilizer, bag after bag
Doris Driscoll
NW
They came back every night for my fertilizer, bag after bag
Doris Driscoll
SH
Steve Harrington changed the price of USS Butterscotch
USS Butterscotch
Price
$2.95
Section
Sundaes
Available
At the counter
Orders
12
SH
Steve Harrington added a menu item, USS Butterscotch
USS Butterscotch
Price
$2.95
Section
Sundaes
Available
At the counter
Orders
12
JB
Ticket #101

Every magnet on the store display fell off at once, then did it again an hour later.

JB
Ticket #101

Every magnet on the store display fell off at once, then did it again an hour later.

Saturday

DD
Doris Driscoll phoned the Hawkins Post about the rats
DH
Dustin Henderson wrote Cerebro reception log
Cerebro reception log
STATION: CEREBRO / WEATHERTOP
CALL TO UTAH .......... NO REPLY
UNEXPECTED SIGNAL .... VOICE / RUSSIAN
MESSAGE .............. REPEATING
NEXT STEP ............ KEEP THE TAPE
DH
Record the broadcast

Tape the Russian message Cerebro picked up on Weathertop.

DH
Dustin Henderson radioed Suzie 3 times
MMLSWB
Max Mayfield, Lucas Sinclair, Will Byers and 2 more checked in at Weathertop
JT
Jasper Tey starred cerebro
cerebro
About
The radio built at camp, to reach Utah from Weathertop
Visibility
Public
DH
Dustin Henderson came home to the Party

Friday

JB
Ticket #102

The power went out across town this evening. It came back on its own a minute later.

MMLSMW
RB
SH
Steve Harrington served 2 orders
JT
Jasper Tey merged 3 pull requests into cerebro
DH
cerebro
About
The radio built at camp, to reach Utah from Weathertop
Visibility
Public

Jun 26, 1985

S
Stripe marked Scoops Ahoy invoice #1983 paid
Scoops Ahoy invoice #1983
Amount
$455.25
Billed to
Starcourt Mall
Reference
INV-1983

Jun 19, 1985

S
Stripe marked Scoops Ahoy invoice #1982 paid
Scoops Ahoy invoice #1982
Amount
$388.00
Billed to
Starcourt Mall
Reference
INV-1982

Jun 12, 1985

S
Stripe marked Scoops Ahoy invoice #1981 paid
Scoops Ahoy invoice #1981
Amount
$412.50
Billed to
Starcourt Mall
Reference
INV-1981

Jun 10, 1985

JB

hawkins-post-internship-agreement.pdf · 60 KB · application/pdf

NW

hawkins-post-internship-agreement.pdf · 60 KB · application/pdf

Jun 7, 1985

RB

scoops-ahoy-employment-contract.pdf · 47 KB · application/pdf

SH

scoops-ahoy-employment-contract.pdf · 47 KB · application/pdf

Dec 15, 1984

WB
Will Byers went to the Snow Ball
MM
Max Mayfield went to the Snow Ball
LS
Lucas Sinclair went to the Snow Ball
DH
Dustin Henderson went to the Snow Ball
E
Eleven went to the Snow Ball
MW
Mike Wheeler went to the Snow Ball

Nov 30, 1984

BH
Bernard Hess signed the Hess farm sale

hess-farm-sale-agreement.pdf · 129 KB · application/pdf

Nov 3, 1984

BN
Bob Newby found the X on Will's drawings
Someone put 2 items on the menu
BN
Bob Newby wrote Hawkins Lab door override
Hawkins Lab door override
10 REM DOOR OVERRIDE — ILLUSTRATIVE BASIC
20 INPUT "SECURITY CODE"; C$
30 IF C$ = "" THEN GOTO 20
40 PRINT "MANUAL OVERRIDE REQUESTED"
50 FOR D = 1 TO 4
60 PRINT "DOOR"; D; " RELEASE REQUEST SENT"
70 NEXT D
80 END

Nov 1, 1984

Oct 29, 1984

Oct 28, 1984

MM
Max Mayfield set a new high score on Dig Dug at the Palace Arcade

Dec 24, 1983

JH
Jim Hopper left Eggo waffles in the woods for Eleven

Nov 12, 1983

ELSDH
Eleven, Lucas Sinclair, Dustin Henderson and 1 more checked in at Hawkins Middle School

Nov 10, 1983

E
Eleven took Eggo waffles from Bradley's Big Buy

Nov 9, 1983

JB
Joyce Byers wrote Joyce’s alphabet wall
Joyce’s alphabet wall
  • A B C D E F G H
  • I J K L M N O P Q
  • R S T U V W X Y Z

Nov 7, 1983

BH
Benny Hammond phoned social services
E
Eleven had a burger at Benny's Burgers

The read mode determines which groups the query returns:

ModeReturns
log()one item per activity, including each member of a composite
live()one group per activity, chosen from the groups it qualifies for, with repeat as the fallback. The default
summary()one summary item per actor per calendar day (or the period passed to summary()), across verbs. See Retrieving Feeds

Set grouping.curate to false to limit live() to repeats. storyfeed:curate chooses groups for recent activities and runs hourly through Laravel's scheduler.

Grouping Axes ​

Built-In Axes ​

AxisCollapsesSingular Tokens AllowedOne TypeExample Headline
repeatone actor repeating a verb:actor :targetyes":actor placed :count orders with :target"
actorsmany actors, same verb and target:targetno":actors ordered from :target"
targetsone actor across targets:actorno":actor asked about :targets"
objectmany actions on one object:actor :objectyes":actor changed the price of :object :count times"
compositean authored collection story:actor :target :context—see Composites

Headlines for groups marked One Type may go in a Story class or inside Story::for(). Define the others on the verb alone.

Configuring Grouping Thresholds ​

config/storyfeed.php
php
'grouping' => [
    'policy' => [
        'min_actors' => 3,
        'min_targets' => 2,
        'min_target_members' => 3,
        'min_object_members' => 2,
    ],
],
KeyWhat the Axis NeedsDefault
min_actorsactors: this many different actors3
min_targetstargets: this many different targets2
min_target_memberstargets: this many activities3
min_object_membersobject: this many activities on the one object2

Activities below a threshold cannot form that group. They fall back to repeat when no other group qualifies. After changing thresholds, run php artisan storyfeed:curate to re-evaluate existing groups. Changes to an axis's grouping key or newly registered axes require rehashing.

See Grouping Periods to choose the calendar boundary shared by grouped activities.

Defining Group Headlines ​

Pass one headline per axis to grouped(), as in the repeat and actors examples. Where you declare it determines which groups use it.

Definition Scope ​

Written InKeyUsed For
Story::for(Order::class)->verb('place'), or a Story class's place()repeat.order.placegroups of orders
Story::verb('place')repeat.placegroups of any type

Storyfeed tries the key with the group's type first, then the key without it.

Choose the declaration location from the axis's shared values. The default grouping period is one day.

Shared ValuesAxisHeadlineDeclared On
one actor, verb, target and object type, on one dayrepeat:actor made :count order placements with :targetthe type
one verb and target on one day, from several actorsactors:actors ordered from :targetthe verb
one actor, verb and object, on one dayobject:actor changed the price of :object :count timesthe type
one actor and verb on one day, across several targetstargets:actor asked :count questions about :targetsthe verb

A type-level headline may name that type because every member shares it. A verb-level headline may describe several object types, so avoid naming a particular type. :count counts activities, not distinct objects; see the repeated-order example for wording that keeps this distinction visible.

Singular and Plural Tokens ​

Singular TokenPlural TokenEntity Role
:actor:actorswho acted
:object:objectswhat the activity acted on
:target:targetswhat the activity was directed at
:context:contextsthe surrounding container
:origin:originsthe source
:result:resultsthe produced entity
:instrument:instrumentsthe tool or service used

A plural token displays a few names and a count of the rest. See Rendering for details. :count is the number of activities in the group.

A singular token is allowed only when every activity shares that role, as shown in Singular Tokens Allowed above. Plural tokens are allowed in any group headline.

php
// a repeat group: one customer, many dishes
':actor changed the price of :object :count times' // ✗ which dish? fails when stories compile
':actor changed :count prices'                      // ✓
':actor changed :count prices on :targets'          // ✓ lists fit every member

Both headlines use allowed tokens, but three lists make the first hard to read:

php
// an actors group: every member shares :target
':actors placed :objects with :targets' // ✗ three lists of names
':actors ordered from :target'          // ✓ one list, one shared role

Use one list per headline and replace the others with :count.

Missing Roles ​

A plural token lists only filled roles. The targets axis groups by actor, verb, and calendar period (a day by default). An activity with an empty target can therefore join the group: it contributes to :count but adds no name.

php
// a targets group of 5 members, 2 of them carrying a target
':actor asked about :count dishes'  // ✗ five members, two dishes
':actor asked about :targets'       // ✓ names the two there are

The first headline says there are five dishes when only two are recorded. Storyfeed does not check nouns beside :count.

Fallback Nouns ​

With no group headline, a group tries the single-activity headline. A role that differs across the group becomes a plain noun, such as "dishes", when all its entities are one type. Otherwise the group has no headline, and your renderer handles it.

Give a type its noun:

routes/feed.php
php
use App\Models\MenuItem;
use Storyfeed\Facades\Story;

Story::for(MenuItem::class)->fallback()->noun('dish|dishes');

Supply both forms; Storyfeed does not derive plurals. For more plural forms, add pipe segments. See Localization for translated nouns. The default is item|items.

The entity count selects the form: FeedNoun::form('dish|dishes', 7) returns dishes. The headline :actor put :object on the menu becomes :actor put dishes on the menu. The noun is plain text; :actor remains a link.

Defining Custom Axes ​

Define a custom axis with the fields activities must share and the threshold they must meet:

app/Providers/AppServiceProvider.phpboot()
php
use Storyfeed\Facades\Storyfeed;
use Storyfeed\Grouping\Axis;

Storyfeed::axes([
    Axis::make('scene')
        ->key('v:ca!:cid!:d')                      // same verb, same context, same day
        ->eligibleWhenDistinct('actor', min: 2),
]);

Here, scene groups activities in the same context, such as three customers asking about dishes in one shop. Use $group->axis('scene', …) inside grouped() to define its headline, or $group->any(…) to match any axis.

Axis Keys ​

Separate shared fields with :. Add ! after a field to exclude activities where it is empty.

RoleType FieldId Field
actoraaaid
objectoaoid
targettatid
contextcacid
originoraorid
resultrarid
instrumentiaiid

Add v to group by verb and d to group by calendar period (a day by default). A singular token such as :context requires both of that role's fields in the key. Without v, the group may contain several verbs, so define its headline on a key without a verb (scene.* or *.*).

Prioritizing Axes ​

New axes have the lowest priority. To place one before a built-in axis:

app/Providers/AppServiceProvider.phpboot()
php
use Storyfeed\Facades\Storyfeed;

Storyfeed::axes([$scene], before: 'repeat');

Released under the MIT License.