{ "description": "Group-apps registry vectors: which trusted first-party apps a group chat has added. The registry is fold state from wire events in the group's own encrypted log — the server never learns which app was added. Source of truth: web/static/apps.js (the groupapps system app).", "app": "groupapps", "source": "web/static/apps.js (installsApp — one registry for every shared place; this file pins its groupapps event family)", "state_shape": { "apps": [ { "id": "string (the added app's name, e.g. \"todo\")", "by": "string (sender name)", "ts": "number (epoch ms)" } ] }, "notes": [ "The item id IS the app name, so a duplicate add (any member, any refold) is a no-op and a remove needs no lookup.", "Unknown app names fold into state unchanged in shape — clients filter to registered, groupCapable apps at READ time (groupAppsFor), so old and new clients converge on the same log.", "Chat projection: the group fold prefixes the actor's name onto the projection text, so the rendered action line is e.g. \"Ana added To-do to this chat\". The expected_chat_projection below is the raw groupFeedMessage() output, pre-prefix — same convention as todo.json (whose companion app projects via chatMessage instead).", "An app's own events posted into a group thread fold with the app's ordinary vectors (todo.json etc.) — same events, same reducer, keyed by the group thread instead of the personal one." ], "vectors": [ { "name": "groupapps_add", "description": "A member adds a trusted app to the group.", "event": { "type": "groupapps.add", "app": "todo", "name": "Ana", "ts": 1700000000000 }, "expected_state_update": { "apps": [ { "id": "todo", "by": "Ana", "ts": 1700000000000 } ] }, "expected_chat_projection": { "text": "added To-do to this chat", "action": true } }, { "name": "groupapps_add_duplicate", "description": "Re-adding an already-added app is a no-op (idempotent by app name).", "event": { "type": "groupapps.add", "app": "todo", "name": "Bob", "ts": 1700000005000 }, "initial_state": { "apps": [ { "id": "todo", "by": "Ana", "ts": 1700000000000 } ] }, "expected_state_update": { "apps": [ { "id": "todo", "by": "Ana", "ts": 1700000000000 } ] }, "note": "The original add's attribution is kept — a duplicate never rewrites by/ts." }, { "name": "groupapps_remove", "description": "A member removes an added app from the group.", "event": { "type": "groupapps.remove", "app": "todo", "name": "Ana", "ts": 1700000010000 }, "initial_state": { "apps": [ { "id": "todo", "by": "Ana", "ts": 1700000000000 } ] }, "expected_state_update": { "apps": [] }, "expected_chat_projection": { "text": "removed To-do from this chat", "action": true } }, { "name": "groupapps_add_malformed", "description": "An add with no app name (malformed or hostile) folds to a no-op, never a crash.", "event": { "type": "groupapps.add", "name": "Mallory", "ts": 1700000020000 }, "initial_state": { "apps": [] }, "expected_state_update": { "apps": [] } }, { "name": "groupapps_remove_missing", "description": "Removing an app that isn't added (a cancel racing a remove, a refold) is a no-op.", "event": { "type": "groupapps.remove", "app": "todo", "name": "Ana", "ts": 1700000030000 }, "initial_state": { "apps": [] }, "expected_state_update": { "apps": [] } } ] }