Skip to content

Commit 38dfbe0

Browse files
InfantLabclaude
andcommitted
chore(release): v0.6.2 — ourmoji admin module-toggle endpoint
Bumps version to 0.6.2 and adds the admin endpoint used to enable the restricted Ourmoji module per-user in production. Also adds user-facing module docs and the CHANGELOG entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 40db869 commit 38dfbe0

5 files changed

Lines changed: 197 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.6.2] - 2026-04-20
11+
12+
### Added
13+
14+
- **Ourmoji module** (restricted / feature-flagged): daily emoji oracle + multi-night dream-telepathy experiment runs, nightly role assignments, morning dream capture with voice or text, reveal flow, and experiment-run management page. Hidden unless the user has the `ourmoji` feature flag.
15+
- **Admin module-toggle endpoint**: `PATCH /api/v1/admin/users/:id/modules` merges per-user feature flags (e.g. `{ "ourmoji": true }`) into `user_preferences.enabled_modules`. Requires `admin:users:write`.
16+
1017
## [0.6.1] - 2026-03-29
1118

1219
### Theme: PWA, Push Notifications & Accessibility (WCAG 2.2 AA)

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tada",
3-
"version": "0.6.1",
3+
"version": "0.6.2",
44
"private": true,
55
"type": "module",
66
"description": "Personal lifelogger - Track Activities, Discover Achievements",
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* PATCH /api/v1/admin/users/:id/modules
3+
*
4+
* Toggle per-user feature module flags (e.g. ourmoji).
5+
* Merges the supplied flags into the existing `enabled_modules` JSON field
6+
* on `user_preferences`, creating the row if it doesn't exist.
7+
*
8+
* Body: { "ourmoji": true, "someOtherFlag": false }
9+
*/
10+
11+
import * as z from "zod";
12+
import { nanoid } from "nanoid";
13+
import { requireAdmin } from "~/server/utils/admin";
14+
import { success, notFound, validationError } from "~/server/utils/response";
15+
import { logAuthEvent } from "~/server/utils/authEvents";
16+
import { db } from "~/server/db";
17+
import { users, userPreferences } from "~/server/db/schema";
18+
import { eq, sql } from "drizzle-orm";
19+
20+
const bodySchema = z.record(z.string(), z.boolean()).refine(
21+
(obj) => Object.keys(obj).length > 0,
22+
{ message: "At least one module flag is required" },
23+
);
24+
25+
export default defineEventHandler(async (event) => {
26+
requireAdmin(event, "admin:users:write");
27+
28+
const userId = getRouterParam(event, "id");
29+
if (!userId) {
30+
throw createError(notFound(event, "User"));
31+
}
32+
33+
// Verify user exists
34+
const user = await db
35+
.select({ id: users.id })
36+
.from(users)
37+
.where(eq(users.id, userId))
38+
.limit(1);
39+
40+
if (user.length === 0) {
41+
throw createError(notFound(event, "User"));
42+
}
43+
44+
const body = await readBody(event);
45+
const parseResult = bodySchema.safeParse(body);
46+
47+
if (!parseResult.success) {
48+
const errors: Record<string, string[]> = {};
49+
for (const issue of parseResult.error.issues) {
50+
const path = issue.path.join(".") || "_root";
51+
if (!errors[path]) errors[path] = [];
52+
errors[path].push(issue.message);
53+
}
54+
throw createError(validationError(event, errors));
55+
}
56+
57+
const flags = parseResult.data;
58+
59+
// Fetch existing preferences row
60+
const existing = await db
61+
.select()
62+
.from(userPreferences)
63+
.where(eq(userPreferences.userId, userId))
64+
.limit(1);
65+
66+
let previousModules: Record<string, boolean> = {};
67+
let updatedModules: Record<string, boolean>;
68+
69+
if (existing.length === 0) {
70+
// Create preferences row with the supplied flags
71+
updatedModules = { ...flags };
72+
await db.insert(userPreferences).values({
73+
id: nanoid(),
74+
userId,
75+
enabledModules: updatedModules,
76+
});
77+
} else {
78+
// Merge flags into existing enabled_modules
79+
const current = existing[0].enabledModules;
80+
previousModules =
81+
current && typeof current === "object" ? { ...current } : {};
82+
updatedModules = { ...previousModules, ...flags };
83+
84+
await db
85+
.update(userPreferences)
86+
.set({
87+
enabledModules: updatedModules,
88+
updatedAt: sql`(datetime('now'))`,
89+
})
90+
.where(eq(userPreferences.userId, userId));
91+
}
92+
93+
const auth = event.context.auth!;
94+
await logAuthEvent({
95+
event,
96+
userId: auth.userId,
97+
eventType: "admin:user_updated",
98+
metadata: {
99+
targetUserId: userId,
100+
action: "modules_updated",
101+
changes: flags,
102+
previousModules,
103+
},
104+
});
105+
106+
return success(event, { enabledModules: updatedModules });
107+
});

docs/modules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Entry types define **how** an activity is recorded. Each module provides its own
1414
| [Sessions](./sessions.md) | `timed` | Timer for meditation and focused practices |
1515
| [Tally](./tally.md) | `tally` | Count-based tracking for discrete activities |
1616
| [Rhythms](./rhythms.md) || Graceful chains that celebrate consistency |
17+
| [Ourmoji](./ourmoji.md) ⚠️ | `ourmoji`, `dream-experiment` | Restricted: daily emoji oracle + shared dream experiments |
1718

1819
## Data Flow Modules
1920

docs/modules/ourmoji.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Ourmoji
2+
3+
> ⚠️ **Restricted module — not on general release.**
4+
> Ourmoji is gated behind a per-user feature flag and currently enabled only for a small group of collaborators. It is not part of the public Ta-Da! experience and may change or be removed without notice.
5+
6+
**A daily emoji oracle and shared dream experiment.**
7+
8+
Each morning an emoji is drawn from a Sacred Set and paired with a poetic reflection that references the moon phase and the Wheel of Year. On enrolled nights, paired participants run a structured dream-telepathy experiment: a Sender focuses on a target emoji before sleep, a Receiver records their dream and guesses the emoji the next morning.
9+
10+
## What it does
11+
12+
- **Daily Ourmoji** — A first-class daily entry showing today's emoji, reflection, moon phase, and Wheel of Year context
13+
- **Calendar view** — Scrollable history of past Ourmojis as a grid, each tappable for full reflection
14+
- **Dream experiments** — Multi-night runs pairing participants as Sender / Receiver / Control / Rest
15+
- **Nightly assignment** — Roles assigned automatically at 21:00 in the earliest participant timezone, blinded to the Receiver
16+
- **Morning dream capture** — Voice or text dream recording followed by an emoji guess, locked once submitted
17+
- **Reveal flow** — After both participants submit, the target emoji is revealed alongside the recorded dream
18+
- **Neutral progress stats** — During active runs only nights-completed and submission counts are shown; hit rates and p-values are hidden until the run finishes
19+
- **Experiment management** — Create, pause, resume, and review experiment runs from a dedicated page
20+
21+
## Philosophy
22+
23+
Ourmoji is a research-flavoured ritual layer on top of Ta-Da!. Where the rest of the app celebrates everyday accomplishments, Ourmoji explores the symbolic and oneiric edge — daily oracles, shared dreams, and rigorously logged "what if?" experiments. It is intentionally narrow in audience: the goal is to learn from a small cohort before deciding whether anything here belongs in the general release.
24+
25+
## Access
26+
27+
Ourmoji is hidden unless the authenticated user has the `ourmoji` feature flag. Without the flag:
28+
29+
- No navigation entry appears
30+
- API endpoints under `/api/ourmoji/**` return as if the feature does not exist
31+
- No error or upsell is shown
32+
33+
### Enabling for a user
34+
35+
An admin can toggle module flags via the admin API:
36+
37+
```http
38+
PATCH /api/v1/admin/users/:userId/modules
39+
Content-Type: application/json
40+
41+
{ "ourmoji": true }
42+
```
43+
44+
The body is a `Record<string, boolean>` — flags are merged into the user's existing `enabled_modules` preferences. Set a flag to `false` to disable it. The endpoint creates a `user_preferences` row automatically if the user doesn't have one yet.
45+
46+
Requires admin auth (`ADMIN_USER_IDS` env var) with `admin:users:write` permission.
47+
48+
## Module definition
49+
50+
| Field | Value |
51+
|-------|-------|
52+
| Types | `ourmoji`, `dream-experiment` |
53+
| Label | Ourmoji |
54+
| Status | Restricted (feature-flagged) |
55+
| Requires | `ourmoji` feature flag |
56+
| Notifications | Web Push (nightly assignment, morning prompt) |
57+
58+
## Code
59+
60+
| Path | Purpose |
61+
|------|---------|
62+
| `app/modules/entry-types/ourmoji/index.ts` | Daily Ourmoji entry type registration |
63+
| `app/modules/entry-types/dream-experiment/index.ts` | Dream experiment entry type registration |
64+
| `app/pages/ourmoji.vue` | Daily Ourmoji + calendar page |
65+
| `app/pages/ourmoji/experiments.vue` | Experiment run management page |
66+
| `app/components/ourmoji/` | Daily card, calendar, dream capture, reveal, experiment manager components |
67+
| `app/composables/useOurmoji.ts` | Client state for daily + calendar |
68+
| `app/composables/useDreamExperimentFlow.ts` | Client state for nightly assignment + morning flow |
69+
| `app/server/api/ourmoji/` | REST endpoints (daily, calendar, experiments, submissions) |
70+
| `app/server/services/ourmoji/` | Daily ingest, scheduling, randomization, notifications, submissions |
71+
| `app/server/plugins/ourmoji-scheduler.ts` | Nitro plugin driving nightly assignment sweeps |
72+
| `app/server/db/migrations/0023_ourmoji_module.sql` | Schema for experiments, assignments, submissions |
73+
| `app/utils/ourmoji/` | Sacred Set + Wheel of Year helpers |
74+
75+
## Specification
76+
77+
Full design lives in [`specs/013-ourmoji-module/`](../../specs/013-ourmoji-module/) — see `spec.md`, `data-model.md`, and `contracts/openapi.yaml`.
78+
79+
---
80+
81+
[Back to modules](./README.md)

0 commit comments

Comments
 (0)