Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fa00adb
Merge pull request #499 from ChronoAIProject/sync/post-release-v0.7.2
chronoai-shining May 13, 2026
f2f9f6a
feat: admin broadcast notifications inbox with bilingual markdown (#501)
chronoai-shining May 14, 2026
d183cff
feat: targeted broadcasts + click-to-popup markdown viewer (#504)
chronoai-shining May 14, 2026
50a1bdb
fix(web): i18n coverage for Playground + Generative + Quota chip + Mo…
chronoai-shining May 14, 2026
815b776
fix(web): break NotificationDetailModal mark-read render loop (#510)
chronoai-shining May 14, 2026
3c9704e
feat(web): landing-page launch-celebration popup (#511) (#512)
chronoai-shining May 14, 2026
6620283
fix(web): LaunchCelebrationPopup — flip to obsidian surface for contr…
chronoai-shining May 14, 2026
136383c
fix(web): LaunchCelebrationPopup — Forge Workshop redesign for launch…
chronoai-shining May 14, 2026
cecf3d6
fix(web): LaunchCelebrationPopup — plate as sibling fixes stacking-co…
chronoai-shining May 14, 2026
a289297
feat(web): LaunchCelebrationPopup — explain NyxID SSO flow next to in…
chronoai-shining May 14, 2026
70a580c
feat(web): LaunchCelebrationPopup — add fulfillment note covering pos…
chronoai-shining May 14, 2026
fef220f
fix(web): rail tabs — icon + tooltip handles, fix CJK upside-down lab…
chronoai-shining May 14, 2026
4c3d5dd
feat(web): LaunchCelebrationPopup — molten edge flow + caption consis…
chronoai-shining May 14, 2026
3a3c017
fix(web): open detail modal for non-broadcast notifications missing a…
chronoai-shining May 14, 2026
0f714a1
refactor(web): preview + chat drawers — flatten layout, pin actions, …
chronoai-shining May 14, 2026
1038bfc
chore(api): mask redemption code with **** instead of ellipsis (#549)…
chronoai-shining May 14, 2026
62eea03
refactor(web): drawer consolidation — playground absorbs Skill tab, g…
chronoai-shining May 14, 2026
6e92cc8
feat(web): pin launch-celebration content to top of /news (#553) (#554)
chronoai-shining May 14, 2026
da34bd1
docs: prep release v0.8.0 (#556)
chronoai-shining May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/admin-broadcast-notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ornn-api": minor
"ornn-web": minor
---

Admin broadcast notifications (#500). Admins can now author bilingual (EN + ZH) markdown notifications from `/admin/broadcasts` that land in every user's `NotificationBell` inbox; edits propagate to all users, hard delete clears the message and cascades read receipts. Backend exposes admin-guarded CRUD at `/api/v1/admin/broadcasts/*` and merges broadcasts into the existing `/api/v1/notifications` feed under a `source: "broadcast"` discriminator, with per-user read state stored in a separate `broadcast_read_receipts` collection.
9 changes: 9 additions & 0 deletions .changeset/drawer-consolidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"ornn-web": patch
---

Playground Package drawer absorbs the redundant Skill drawer (`SkillPackagePreview` now renders the same identity strip on both pages, so two tabs showing the same info was confusing). Drawer width on Playground unified to the gen page's `min(960px, 65vw)`. Fixed a flex-wrapper bug where the Playground Package drawer's file viewer overflowed past the footer. `ResizablePanes` default split 26 % → 32 % so typical skill folder names like `ornn-agent-manual-cli` no longer truncate.

On the AI Skill Generation page, the Package rail tab now pulses ember-accent (steady ring + animated `ping` ripple + dot) when a new iteration lands while the drawer is closed — so multi-turn refinement gives a visible "new artifact ready" cue. Clears the moment the user opens the drawer.

Closes #551.
6 changes: 6 additions & 0 deletions .changeset/fix-notification-modal-mark-read-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ornn-api": patch
"ornn-web": patch
---

Fix click-to-popup broadcast notification (#502) crashing with React error #185 (Maximum update depth exceeded). The mark-read effect inside `NotificationDetailModal` re-fired on every parent rerender because the parent passed an inline arrow callback. Modal now stores the callback in a "latest ref" and tracks the last marked id, so mark-read fires exactly once per opened broadcast no matter how the parent rerenders.
5 changes: 5 additions & 0 deletions .changeset/i18n-playground-skillgen-quota-modelpicker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

i18n coverage — Playground + Generative skill builder + shared quota / model picker chips (#503). Adds the missing `playground.*` and `generative.*` keys (hero, starters, drawer hint, drawer tabs, pin/unpin, kbHint, env-var hint, validation panel, empty-preview hero/hint) to `en.json` / `zh.json`, and routes `QuotaInline`, `QuotaChip`, and `ModelPicker` through `useTranslation` under new `quota.*` and `modelPicker.*` namespaces. Switching the UI to Chinese now translates these surfaces fully; English copy is unchanged.
5 changes: 5 additions & 0 deletions .changeset/landing-launch-celebration-popup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

Landing-page launch-celebration popup. Hardcoded, bilingual (en / zh) modal that fires on every visit to `/` for both anonymous and signed-in users, announcing the public-launch free-credit promo (200 Playground + 200 Skill Generation credits for the first 500 users who star the GitHub repo and sign in). Dismissal is session-only — no localStorage write — so the popup reappears on any return visit until the component is unmounted from `LandingPage` at the end of the launch window. Independent of the dynamic announcements collection on purpose.
5 changes: 5 additions & 0 deletions .changeset/launch-celebration-on-news-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

Pin launch-celebration content to top of `/news` (#553). Mirrors the hardcoded landing-page `LaunchCelebrationPopup` as a permanent News-archive entry so returning visitors who reach `/news` via the navbar — not the landing — still see the public-launch free-credit promo. New `LaunchCelebrationNewsEntry` is a non-modal, `card-impression`-styled article that reuses the popup's `landing.launchPopup.*` i18n keys (EN + ZH inherited verbatim), keeps the click-to-copy NyxID invite chip and "Star on GitHub" CTA, and stamps ahead of the dynamic announcements feed. Independent of `/announcements/active`; remove the JSX + import from `NewsPage.tsx` when the offer ends.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-contrast-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup contrast fix (#513). Flips the popup surface from ember orange → obsidian panel with an ember accent border, so the inline GitHub repo link (previously `ember-deep` on `ember`, effectively invisible) reads clearly in ember on dark, and every body paragraph sits at near-maximum contrast against the surface. AnnouncementPopup keeps its ember plate; only the launch-day popup flips.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-explain-nyxid-sso.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup — explain the NyxID SSO flow next to the invite code (#519). The popup showed the invite code (`NYX-2XXJI08A`) under a mono label "**NyxID invite · first-time users**" with no further context, so users couldn't tell what to do with the code: where they'd be asked for it, that Ornn's Sign In redirects to NyxID (a separate product — our identity provider), or that they still need to choose GitHub on the NyxID page after pasting the code. Added a short explanatory paragraph below the code chip walking through the actual flow (click Sign In → redirected to NyxID → paste code → continue with GitHub). New i18n key `landing.launchPopup.inviteHelp` in both `en.json` and `zh.json`; layout slot is the previously empty space between the code chip and the "Limited slots" warning.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-fulfillment-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup — add fulfillment note covering post-redemption flow (#524). Follow-up to #519 (which explained the NyxID SSO login flow). Users still didn't know what happens *after* they sign in and star the repo: how long until the code arrives (24h), where it lands (in-app notification inbox, not email), where to apply it (profile dropdown → "Redeem code"), or where to get help if redemption fails. Added a second body paragraph directly below the existing NyxID-flow paragraph that walks through delivery timing, the bell-icon notification inbox, the profile-dropdown redeem entry, and a discussions-thread link for support. Three new i18n keys (`fulfillmentBefore`, `fulfillmentLinkLabel`, `fulfillmentAfter`) following the same split pattern as `step1Before`/`step1After` so the discussions URL renders inline.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-offer-tile-flow-and-caption.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup — molten edge flow on offer tiles + caption consistency fix (#526). The two "200 credits" tiles (Playground + Skill Generation) were flat matte rectangles and the eye drifted past them, even though they're the whole reason to read the popup. Added a "molten edge flow" effect: a single bright ember-to-gold comet travels around each tile's perimeter over 5s (staggered by 50% across the two tiles so they read as independent objects), with a soft outer ember halo so the tile radiates heat at rest. Pure CSS via `@property --offer-angle` + conic-gradient + mask-composite; no JS, no Framer Motion. Reduced-motion safe — rotation suppressed, ring falls back to a static diagonal molten gradient. Also fixed a caption inconsistency: both tiles spend the same conversation-credit pool but `credit2Caption` was labeled `"Drafts"` / `"创建额度"` while `credit1Caption` was `"Conversations"` / `"对话额度"`, which read as two different credit types. Aligned `credit2Caption` to match.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup redesign (#515) — full pass following DESIGN.md Forge Workshop language. Swaps every raw-var arbitrary class for semantic theme-aware tokens (`bg-card`, `text-strong`, `border-accent`, …) so dark + light both resolve cleanly. New visual structure: two-up offer tiles with big "200" numerals as the visual anchor, numbered redemption steps (01 / 02) with ember-mono numerals, click-to-copy invite-code chip in molten-gold mono, welded-seam divider with ember rivets, `★ Star on GitHub` primary CTA. Press-down behavior is centralized via `.cta-letterpress` — no more inline `box-shadow` strings (a DESIGN.md review-blocker). Closes #515.
5 changes: 5 additions & 0 deletions .changeset/launch-popup-stacking-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ornn-web": patch
---

LaunchCelebrationPopup — fix letterpress plate stacking-context bug (#517). The shadow plate was a child of the card div, with `z-index: -10` to sit behind. But the card itself created a stacking context via `position: relative + z-index: 10`, so the plate's negative-z child painted **over** the card's bg instead of behind it — invisible in dark mode (both colors are dark ember tones) but catastrophic in light mode (white card with a rust-red plate covering it). Restructured so the plate is a sibling of the card inside a positioning wrapper; paint order = document order; no z-index needed.
9 changes: 9 additions & 0 deletions .changeset/notification-modal-opens-non-broadcast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"ornn-web": patch
---

Make `NotificationDetailModal` source-agnostic so clicking a quota notification (or any non-broadcast row without a deep-link) opens it in place instead of silently marking it read. Closes #532.

Quota credit notifications (`quota.credits_granted`) are emitted without a `link` by design — the API service comment explicitly notes there's no good deep-link target — so the `cursor-pointer` row on `/notifications` and the bell popover looked clickable but did nothing visible. Long admin notes (e.g. `Note: Redeemed code Y69H…`) were truncated by the row width with no way to read them.

The modal now branches on `source`: broadcasts keep their bilingual markdown rendering; user-source rows render `title` + plain-text `body` with a category-resolved tag chip (Audit / Quota). The category labels reuse the same hardcoded map that `NotificationsPage` already shipped — separate follow-up if/when we localize them. Both surfaces (full page + bell) now route non-broadcast rows through the modal when the row has body content but no link; rows with a link still navigate (audit deep-links continue to work).
12 changes: 12 additions & 0 deletions .changeset/preview-drawer-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"ornn-web": patch
---

Skill package preview + Playground / Skill-generation drawers — flatten layout, drop the multi-colour badge palette, pin action buttons.

- `SkillPackagePreview` identity strip is now a single flat section (no nested cards). Category renders as mono `[§ CATEGORY]` ember-bracketed, tags as `tag · tag · tag` dot-separated mono in `text-meta`. Drops the cyan/magenta/yellow/green pill palette entirely.
- Both pane headers (`FileTree`, `SkillFileViewer`) lock to `h-9` so the seam is straight; the viewer drops the redundant `FILE` prefix and shows the filename as the header.
- AI Skill Generation drawer widens (`520px` → `min(960px, 65vw)`), clears the 60 px navbar (`top-4` → `top-[68px]`), and pins the `Start over` / `Save skill` buttons at the bottom — the preview panes scroll internally instead of scrolling the whole drawer.
- Playground's three drawers (Skill / Env / Package) pick up the same voice: flat identity strip, mono status indicators (no rainbow `Badge`), `Skill page` registry link pinned to the drawer footer. Drawer widens to `min(560px, 40vw)`.

Closes #547.
9 changes: 9 additions & 0 deletions .changeset/rail-tab-icon-handles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"ornn-web": patch
---

Fix the right-edge rail tabs on Playground and the AI Skill Generation page — labels were rendering upside-down for CJK because of a `writing-mode: vertical-rl` + `rotate(180deg)` combo intended only for vertical English.

Replaced the rotated text with icon-only tab handles (`SkillIcon`, `EnvIcon`, `PackageIcon`) and a horizontal mono-uppercase tooltip that fades in on hover (`[§ SKILL]` / `[§ ENV]` / `[§ PACKAGE]`, matching the drawer header voice). All three tabs are now equal-height — previously `PACKAGE` was ~1.7× taller than `ENV` because of letter count. `aria-label` keeps using the i18n string so screen readers stay locale-correct.

Closes #522.
7 changes: 7 additions & 0 deletions .changeset/redemption-note-mask-stars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ornn-api": patch
---

Swap the privacy-mask glyph in redemption-code grant notes from `…` (U+2026) to `****`. The ellipsis is unambiguous in the audit log but reads as "text truncated, click to see more" in the new `/notifications` detail modal (#532), so users kept asking why the full code was hidden. The mask now signals *masked* rather than *truncated*. Only the first four chars of the code still leak — privacy intent unchanged. Closes #549.

Affects new audit + notification rows generated after this lands; historical rows continue to carry `…` (no migration — old rows are clearly QUOTA-tagged and redemption-sourced, no semantic loss).
2 changes: 2 additions & 0 deletions .changeset/release-notes-v0.8.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .changeset/targeted-broadcasts-and-modal-viewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ornn-api": minor
"ornn-web": minor
---

Targeted broadcasts + click-to-popup markdown viewer (#502). Builds on #500. Admins can now choose between broadcasting to all users (existing behaviour) and targeting specific users by email. Recipients are immutable after create — edits only touch the bilingual title/body, deletes still cascade read receipts. End-user side: clicking a broadcast notification in the bell or `/notifications` page now opens a modal that renders the full bilingual markdown body (existing audit / quota notifications keep their navigate-to-link behaviour). The merged `/notifications` feed, unread count, mark-read, and mark-all-read all transparently filter targeted broadcasts so non-recipients never see them.
20 changes: 20 additions & 0 deletions .github/release-notes-20260516.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Fixed

- Click-to-popup notification modal no longer crashes on broadcasts.
- Quota credit notifications open in a readable modal instead of silently marking read.
- Right-edge rail tabs use icons with tooltips, fixing upside-down CJK labels.
- Few technical bugs fixed.

## New Feature

- Admin broadcast notifications — bilingual EN+ZH markdown, broadcast-all or target by email.
- Click any notification to open a full bilingual markdown viewer modal.
- Launch-day celebration popup on landing and pinned to top of News, walking new users through the free-credit promo and NyxID SSO redemption flow.
- Technical enhancement.

## Changed

- Playground and skill-generation drawers unified — flatter layout, pinned actions, restrained palette.
- Playground, skill builder, quota chip, and model picker now fully translate to Chinese.
- Redemption codes in audit notes mask with `****` instead of ellipsis.
- Technical enhancement.
41 changes: 40 additions & 1 deletion ornn-api/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ import { AnnouncementService } from "./domains/announcements/service";
import { createAnnouncementRoutes } from "./domains/announcements/routes";
import { migrateAnnouncementsToBilingual } from "./domains/announcements/migration";

// Domain: Broadcasts (admin-authored notifications, #500)
import { BroadcastRepository } from "./domains/broadcasts/repository";
import { BroadcastService } from "./domains/broadcasts/service";
import { createBroadcastRoutes } from "./domains/broadcasts/routes";
import { backfillBroadcastRecipientUserIds } from "./domains/broadcasts/migration";

// Domain: Analytics
import { AnalyticsRepository } from "./domains/analytics/repository";
import { AnalyticsService } from "./domains/analytics/service";
Expand Down Expand Up @@ -461,7 +467,30 @@ export async function bootstrap(config: SkillConfig): Promise<BootstrapResult> {
"dropLegacyNotificationCategories failed — legacy notification rows may still surface in /notifications until the next deploy",
),
);
const notificationService = new NotificationService({ notificationRepo });
// `broadcastRepo` is constructed in the broadcasts block below; we
// need a reference at NotificationService-build time so the merged
// feed (#500) can left-join read receipts. Reordered so broadcasts
// build first.
const broadcastRepoForNotifications = new BroadcastRepository(db);
void broadcastRepoForNotifications.ensureIndexes().catch((err) =>
logger.warn({ err }, "broadcasts indexes ensureIndexes failed — proceeding anyway"),
);
// One-shot bilingual + targeting backfill for broadcasts. Pre-#502
// docs don't carry `recipientUserIds`; this migration writes an
// explicit `null` on every absent doc so the merged feed can rely
// on a stable `string[] | null` shape. Idempotent; failure is
// non-fatal — the repo mapper's `Array.isArray` guard already
// normalises absent fields to `null` on the read path.
await backfillBroadcastRecipientUserIds(db, logger).catch((err) =>
logger.error(
{ err: err instanceof Error ? err.message : String(err) },
"broadcasts recipientUserIds backfill crashed — mapper fallback will cover reads, retry on next boot",
),
);
const notificationService = new NotificationService({
notificationRepo,
broadcastRepo: broadcastRepoForNotifications,
});
const notificationRoutes = createNotificationRoutes({ notificationService });

// ---- Domain: Announcements (landing-page popup, issue #307) ----
Expand All @@ -483,6 +512,15 @@ export async function bootstrap(config: SkillConfig): Promise<BootstrapResult> {
const announcementService = new AnnouncementService({ repo: announcementRepo });
const announcementRoutes = createAnnouncementRoutes({ announcementService });

// ---- Domain: Broadcasts (admin-authored, fan-out via notifications, #500) ----
// Reuse the same `BroadcastRepository` instance the notifications
// service got — both surfaces share state (admin CRUD + per-user
// feed merge), and a single instance keeps the wiring legible.
const broadcastService = new BroadcastService({
repo: broadcastRepoForNotifications,
});
const broadcastRoutes = createBroadcastRoutes({ broadcastService });

// ---- NyxID Orgs Client — built early so the audit fan-out can expand
// sharedWithOrgs into member rosters when sending consumer notifications.
// Base URL is resolved from settings (`nyxid` section) on every call.
Expand Down Expand Up @@ -915,6 +953,7 @@ export async function bootstrap(config: SkillConfig): Promise<BootstrapResult> {
apiApp.route("/", auditRoutes);
apiApp.route("/", notificationRoutes);
apiApp.route("/", announcementRoutes);
apiApp.route("/", broadcastRoutes);
apiApp.route("/", analyticsRoutes);
apiApp.route("/", searchRoutes);
apiApp.route("/", generationRoutes);
Expand Down
Loading