Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7eb24c5
feat(react): new react SDK
dipankarmaikap Jul 1, 2026
5f61c7d
fix(react): fix broken type declarations for v2 subentries
dipankarmaikap Jul 2, 2026
006d57c
fix(cli): replace Angular env placeholders with actual values (#663)
dipankarmaikap Jul 2, 2026
e67f669
Revert "fix(react): fix broken type declarations for v2 subentries"
dipankarmaikap Jul 2, 2026
7ee5489
feat(react): add /next subpath exports for v2 SDK
dipankarmaikap Jul 2, 2026
e1f2577
chore(react): add use client directive
dipankarmaikap Jul 2, 2026
9ba6f58
chore(react): preserve use client directive
dipankarmaikap Jul 2, 2026
1428111
fix(cli,mapi-client): preserve image dimensions in asset URLs on push…
maoberlehner Jul 6, 2026
7855903
feat(react): add Suspense support for async components in registry
dipankarmaikap Jul 6, 2026
f479038
chore(release): publish
maoberlehner Jul 6, 2026
881ecb9
chore: rich text componnet re exported
dipankarmaikap Jul 6, 2026
1076a3a
fix(astro): circular dependency through StoryblokComponent (#661)
dipankarmaikap Jul 7, 2026
a3d9612
feat: support global assets library in `assets push` and `assets pull…
maoberlehner Jul 8, 2026
52ac6ab
chore: rename ticket prefix WDX to DX (#680)
maoberlehner Jul 8, 2026
fe0dce0
feat(cli): selective components pull/push by filter, group, and tag (…
maoberlehner Jul 8, 2026
8251d2c
chore(release): publish
maoberlehner Jul 8, 2026
32f13b1
feat(richtext): add context data prop and loop prevention to Storyblo…
dipankarmaikap Jul 9, 2026
2765520
fix(cli): match uppercase X in asset dimension segment on push (MNT-1…
LucasKuhn Jul 10, 2026
cc727de
fix(cli): drive single-option empty type by required, not UI setting …
maoberlehner Jul 10, 2026
556d54c
fix: change storyblokEditable to have narrow type (#685)
dipankarmaikap Jul 10, 2026
e299b51
docs(cli): fix types.generate config example using invalid `output` p…
dipankarmaikap Jul 10, 2026
ed19fb7
chore(repo): require scoped commits to prevent version fan-out (#682)
maoberlehner Jul 10, 2026
3e195b1
chore(release): publish
maoberlehner Jul 10, 2026
02bc287
fix(live-preview): narrow storyblokEditable parameter type and tighte…
dipankarmaikap Jul 13, 2026
d386233
chore(release): publish
dipankarmaikap Jul 13, 2026
46d0860
fix(js-client): correct return type of write methods to ISbResponse (…
maoberlehner Jul 13, 2026
f405a66
test(js-client): enforce write method return types via vitest typeche…
maoberlehner Jul 13, 2026
697bf7c
feat(react): new react SDK
dipankarmaikap Jul 1, 2026
35ee086
fix(react): fix broken type declarations for v2 subentries
dipankarmaikap Jul 2, 2026
7502796
Revert "fix(react): fix broken type declarations for v2 subentries"
dipankarmaikap Jul 2, 2026
ae2b696
feat(react): add /next subpath exports for v2 SDK
dipankarmaikap Jul 2, 2026
604e41c
chore(react): add use client directive
dipankarmaikap Jul 2, 2026
7eddd8b
chore(react): preserve use client directive
dipankarmaikap Jul 2, 2026
605076b
feat(react): add Suspense support for async components in registry
dipankarmaikap Jul 6, 2026
80a3ee9
chore: rich text componnet re exported
dipankarmaikap Jul 6, 2026
ef86f33
Merge branch 'feature/new-react-sdk' of https://github.com/storyblok/…
dipankarmaikap Jul 14, 2026
af6e892
chore(deps): regenerate pnpm-lock.yaml after sync with main
dipankarmaikap Jul 14, 2026
c5573ca
feat(react): add StoryblokComponentProps helper and relax registry co…
dipankarmaikap Jul 21, 2026
1017b66
docs(react): add Next.js App Router integration guide for v2
dipankarmaikap Jul 21, 2026
33384ef
fix(react): use children instead of initialContent to preserve RSC st…
dipankarmaikap Jul 22, 2026
fc8fac6
feat(react): debounce live preview re-renders in StoryblokPreview
dipankarmaikap Jul 22, 2026
cbfe96e
docs(react): update guide for new StoryblokPreview API
dipankarmaikap Jul 22, 2026
564f1a0
feat(react): forward extra props from StoryblokComponent and Storyblo…
dipankarmaikap Jul 22, 2026
4173170
feat(react): rename component prop from blok to block in registry
dipankarmaikap Aug 4, 2026
7c17aa9
feat(react): rename StoryblokComponent public prop from blok to block
dipankarmaikap Aug 4, 2026
7866a46
fix(react): use useState<Promise>+use() instead of useState<ReactNode…
dipankarmaikap Aug 4, 2026
dcf758c
fix(react): track last committed content as Suspense fallback in Stor…
dipankarmaikap Aug 5, 2026
a76b1c7
fix(react): replace useState(children) with useState(null) to unblock…
dipankarmaikap Aug 5, 2026
caa59d9
docs(react): update v2 guide for a76b1c7, two-deployment strategy, an…
dipankarmaikap Aug 6, 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: 3 additions & 3 deletions .agents/skills/investigate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ Investigate the following GitHub issue: $ARGUMENTS
### Phase 1: Fetch Issue Details

1. Parse the input:
- `WDX-123` or `linear.app` URL → **Linear ticket**
- `DX-123` or `linear.app` URL → **Linear ticket**
- `#123`, bare number, or `github.com` URL → **GitHub issue**
2. Fetch the ticket:
- **GitHub:** `gh issue view <number> --json title,body,labels,comments,author,createdAt,state`
- **Linear:** `bash .agents/skills/triage/scripts/linear-fetch.sh issue WDX-123`
- **Linear:** `bash .agents/skills/triage/scripts/linear-fetch.sh issue DX-123`
3. Extract: error messages, stack traces, reproduction steps, environment details

### Phase 2: Identify Affected Package(s)
Expand All @@ -47,7 +47,7 @@ For each root cause: the fix (file paths + line numbers), test coverage needed,

## Output

1. Extract identifier from arguments (issue `424` → `424`, ticket `WDX-296` → `WDX-296`, text → slugified)
1. Extract identifier from arguments (issue `424` → `424`, ticket `DX-296` → `DX-296`, text → slugified)
2. `mkdir -p claude-output`
3. Write to `claude-output/investigate-<identifier>.md`
4. Confirm to user: "Investigation written to `claude-output/investigate-<identifier>.md`"
Expand Down
19 changes: 17 additions & 2 deletions .agents/skills/qa-engineer-manual/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ You seed Storyblok QA spaces with predefined test scenarios. Packages might defi
- Storyblok CLI built: `pnpm nx build storyblok`
- `.env.qa-engineer-manual` file in repo root with `STORYBLOK_TOKEN` and `STORYBLOK_SPACE_ID`

> [!NOTE]
> The seed resolves the CLI and the `.storyblok` staging directory from `git rev-parse --show-toplevel`, i.e. the repo of your current working directory. To exercise changes in a worktree, build the CLI in that worktree and run the seed from inside it; otherwise it falls back to the main checkout's `dist`.

```bash
# .env.qa-engineer-manual
STORYBLOK_TOKEN=your_personal_access_token
Expand Down Expand Up @@ -103,6 +106,18 @@ bash .claude/skills/qa-engineer-manual/scripts/cleanup-remote.sh

Deletes all stories, components (except the default `page` component), assets, asset folders, and internal tags in the space. Uses `STORYBLOK_SPACE_ID` from env by default (override with `--space <id>`). This runs automatically before every seed, but can also be used standalone.

### Shared asset libraries

Shared (org-level) asset libraries are global: they belong to the organization and can be shared across spaces, so a full wipe is destructive. Cleanup is scoped by folder membership, not by name. `--shared --library <libraryId>` deletes every shared asset in the library's folder tree, every internal tag scoped to the library, and every child folder. It never deletes the library root folder or any resource outside the given library. Folder scoping is deliberate: transferred assets keep their original names, so a `qa-` name prefix would miss them.

Because this removes all content inside the library regardless of name, only run it against a dedicated QA library, never a shared library that holds real org content.

```bash
bash .claude/skills/qa-engineer-manual/scripts/cleanup-remote.sh --shared --library <libraryId>
```

Inspect a library first with `list.sh --resource shared-assets|shared-folders|shared-tags`. Package guides (for example `packages/cli/test/GUIDE.md`) describe the CLI push/pull workflow against libraries.

### Scenario structure

A scenario is a directory with optional subdirectories for each resource type:
Expand Down Expand Up @@ -143,8 +158,8 @@ Paths are relative to this `SKILL.md`.
| Script | Purpose |
| --- | --- |
| `./scripts/cleanup-local.sh` | Deletes local QA artifacts in `.storyblok/`. |
| `./scripts/cleanup-remote.sh` | Deletes all stories, components (except `page`), assets, asset folders, and internal tags in the space. Accepts `--space <id>`. |
| `./scripts/list.sh` | Lists resources in the QA space. Pass `--resource stories\|assets\|components\|datasources` and optionally `--space <id>`. |
| `./scripts/cleanup-remote.sh` | Deletes all stories, components (except `page`), assets, asset folders, and internal tags in the space. Accepts `--space <id>`. Add `--shared --library <id>` to instead delete all shared resources in the given org library's folder tree, never the root (see "Shared asset libraries" below). |
| `./scripts/list.sh` | Lists resources in the QA space. Pass `--resource stories\|assets\|components\|datasources` and optionally `--space <id>`. For org libraries: `--resource shared-assets\|shared-tags --library <id>` or `--resource shared-folders`. |
| `./scripts/generate-story.sh` | Writes a story JSON to stdout. All fields optional — use flags to override `--slug`, `--name`, `--component`, `--parent-id`, `--is-folder`, `--id`, `--uuid`. |
| `./scripts/generate-asset.sh` | Writes an asset sidecar JSON to stdout. Use `--filename`, `--alt`, `--title`, `--is-private`, `--folder-id`. Pass `--copy-png <path>` to also copy the template PNG to a target path. |

Expand Down
23 changes: 23 additions & 0 deletions .agents/skills/qa-engineer-manual/scripts/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
# Provides:
# load_env — loads .env.qa-engineer-manual and asserts STORYBLOK_TOKEN
# require_space_id — ensures space_id is set (from arg or env), exits if not
# require_library_id — ensures library_id is set (from --library), exits if not
#
# Constants:
# QA_SHARED_PREFIX — name prefix every QA-created shared resource must carry
# so org-global libraries can be cleaned up safely
# (only prefix-matched resources are ever deleted).

# Shared asset libraries are org-global (shared across spaces), so a full wipe
# is unsafe. Every QA-created shared asset, shared folder, and shared tag must
# be named with this prefix; shared cleanup only deletes prefix-matched items.
QA_SHARED_PREFIX="${QA_SHARED_PREFIX:-qa-}"

# ---------------------------------------------------------------------------
# load_env
Expand Down Expand Up @@ -53,3 +64,15 @@ require_space_id() {
exit 1
fi
}

# ---------------------------------------------------------------------------
# require_library_id
# Call after parsing args. Ensures the global `library_id` variable is set
# (a top-level shared asset folder id). Exits with an error if not.
# ---------------------------------------------------------------------------
require_library_id() {
if [ -z "${library_id:-}" ]; then
printf "Missing --library <libraryId> (a top-level shared asset folder id).\n" >&2
exit 1
fi
}
7 changes: 7 additions & 0 deletions .agents/skills/qa-engineer-manual/scripts/cleanup-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ for sid in "${space_ids[@]}"; do
done
done

# Shared asset libraries live under .storyblok/assets/shared/<library_id>/,
# keyed by org-global library ID rather than space ID, so clean the whole subtree.
if [ -d ".storyblok/assets/shared" ]; then
rm -rf ".storyblok/assets/shared"
removed=$((removed + 1))
fi

if [ "${removed}" -eq 0 ]; then
printf "clean\n"
else
Expand Down
158 changes: 158 additions & 0 deletions .agents/skills/qa-engineer-manual/scripts/cleanup-remote.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ set -euo pipefail
#
# Usage:
# bash .claude/skills/qa-engineer-manual/scripts/cleanup-remote.sh --space <spaceId>
#
# Shared (org-level) asset libraries are global, so a full wipe is unsafe.
# --shared cleans every shared resource that belongs to ONE library, scoped by
# folder membership (not by name): all assets in the library's folder tree, all
# internal tags scoped to the library, and all child folders. Transferred assets
# keep their original names, so folder scoping catches them where a name prefix
# would not. It never deletes the library root folder (org context only) or any
# resource outside the given library.
#
# bash .claude/skills/qa-engineer-manual/scripts/cleanup-remote.sh --shared --library <libraryId>

# shellcheck source=_common.sh
source "$(dirname "${BASH_SOURCE[0]}")/_common.sh"
Expand All @@ -14,13 +24,23 @@ load_env
# Parse arguments
# ---------------------------------------------------------------------------
space_id=""
shared_mode=false
library_id=""

while [ "$#" -gt 0 ]; do
case "$1" in
--space)
space_id="$2"
shift 2
;;
--shared)
shared_mode=true
shift 1
;;
--library)
library_id="$2"
shift 2
;;
*)
printf "warning: unknown argument '%s'\n" "$1" >&2
shift 1
Expand All @@ -37,6 +57,144 @@ per_page=100
found_total=0
deleted_total=0

# Deletes a list of ids (one per line) against an endpoint. Treats 2xx and 404
# (already gone) as success. Runs in a command-substitution subshell, so it
# keeps no global state: it prints "<found> <deleted>" for the caller to total.
# Args: <delete_url_base> <label> <query_suffix> <ids>
delete_ids() {
local url_base=$1
local label=$2
local query_suffix="${3:-}"
local ids=$4
local found_batch=0
local deleted_batch=0
while IFS= read -r id; do
[ -z "${id}" ] && continue
found_batch=$((found_batch + 1))
status=$(curl -s -o /dev/null -w "%{http_code}" --retry 3 --retry-delay 1 \
-X DELETE "${url_base}/${id}${query_suffix}" \
-H "Authorization: ${STORYBLOK_TOKEN}")
if [[ "${status}" == 2* || "${status}" == "404" ]]; then
deleted_batch=$((deleted_batch + 1))
else
printf "failed to delete %s/%s (status %s)\n" "${label}" "${id}" "${status}" >&2
fi
done <<< "${ids}"
printf '%s %s' "${found_batch}" "${deleted_batch}"
}

# ---------------------------------------------------------------------------
# Shared (org-level) library cleanup — folder-scoped, never a full wipe
# ---------------------------------------------------------------------------
if [ "${shared_mode}" = true ]; then
require_library_id

folders_url="https://mapi.storyblok.com/v1/spaces/${space_id}/shared_asset_folders"
assets_url="https://mapi.storyblok.com/v1/spaces/${space_id}/shared_assets"
tags_url="https://mapi.storyblok.com/v1/spaces/${space_id}/shared_internal_tags"

# Resolve every folder id in the library's tree (root first, then children).
# Assets are scoped per folder, so the tree drives which assets get deleted.
folders_resp=$(curl -s --retry 3 --retry-delay 1 "${folders_url}/" \
-H "Authorization: ${STORYBLOK_TOKEN}")
tree_ids=$(printf '%s' "${folders_resp}" | node -e '
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
const library = parseInt(process.argv[1]);
const all = data.shared_asset_folders || [];
const parentById = new Map(all.map(f => [f.id, f.parent_id == null ? null : f.parent_id]));
const inLibrary = (id) => {
const seen = new Set();
let cur = id;
while (cur != null && !seen.has(cur)) {
if (cur === library) return true;
seen.add(cur);
cur = parentById.get(cur);
}
return false;
};
const ids = all.filter(f => f.id !== library && inLibrary(f.id)).map(f => f.id);
// Root first so its assets are enumerated even if the folder list is empty.
process.stdout.write([library, ...ids].join("\n"));
' "${library_id}")

# 1. Shared assets in every folder of the library tree (no name filter),
# paginated per folder.
while IFS= read -r folder; do
[ -z "${folder}" ] && continue
while true; do
assets_resp=$(curl -s --retry 3 --retry-delay 1 \
"${assets_url}/?in_folder=${folder}&page=1&per_page=${per_page}" \
-H "Authorization: ${STORYBLOK_TOKEN}")
asset_ids=$(printf '%s' "${assets_resp}" | node -e '
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
process.stdout.write((data.assets || []).map(a => a.id).join("\n"));
')
[ -z "${asset_ids}" ] && break
read -r bf bd <<< "$(delete_ids "${assets_url}" "${folder}/shared_assets" "" "${asset_ids}")"
found_total=$((found_total + bf)); deleted_total=$((deleted_total + bd))
[ "${bd}" -eq 0 ] && break
done
done <<< "${tree_ids}"

# 2. Shared internal tags scoped to the library (no name filter), paginated
# per pass: delete a page, then re-fetch page 1 until none remain.
while true; do
tags_resp=$(curl -s --retry 3 --retry-delay 1 \
"${tags_url}/?asset_folder_id=${library_id}&page=1&per_page=${per_page}" \
-H "Authorization: ${STORYBLOK_TOKEN}")
tag_ids=$(printf '%s' "${tags_resp}" | node -e '
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
process.stdout.write((data.internal_tags || []).map(t => t.id).join("\n"));
')
[ -z "${tag_ids}" ] && break
read -r bf bd <<< "$(delete_ids "${tags_url}" "${library_id}/shared_internal_tags" "?asset_folder_id=${library_id}" "${tag_ids}")"
found_total=$((found_total + bf)); deleted_total=$((deleted_total + bd))
[ "${bd}" -eq 0 ] && break
done

# 3. Shared child folders in the library tree (never the root), deepest first
# so leaf folders are removed before their parents.
while true; do
folders_resp=$(curl -s --retry 3 --retry-delay 1 "${folders_url}/" \
-H "Authorization: ${STORYBLOK_TOKEN}")
folder_ids=$(printf '%s' "${folders_resp}" | node -e '
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
const library = parseInt(process.argv[1]);
const all = data.shared_asset_folders || [];
const parentById = new Map(all.map(f => [f.id, f.parent_id == null ? null : f.parent_id]));
const inLibrary = (id) => {
const seen = new Set();
let cur = id;
while (cur != null && !seen.has(cur)) {
if (cur === library) return true;
seen.add(cur);
cur = parentById.get(cur);
}
return false;
};
const items = all.filter(f =>
f.id !== library && f.parent_id != null && inLibrary(f.id),
);
// Deepest first: a child has more ancestors than its parent.
const depth = (id) => { let d = 0, cur = parentById.get(id), seen = new Set(); while (cur != null && !seen.has(cur)) { d++; seen.add(cur); cur = parentById.get(cur); } return d; };
items.sort((a, b) => depth(b.id) - depth(a.id));
process.stdout.write(items.map(f => f.id).join("\n"));
' "${library_id}")
[ -z "${folder_ids}" ] && break
read -r bf bd <<< "$(delete_ids "${folders_url}" "${library_id}/shared_asset_folders" "" "${folder_ids}")"
found_total=$((found_total + bf)); deleted_total=$((deleted_total + bd))
[ "${bd}" -eq 0 ] && break
done

if [ "${found_total}" -eq 0 ]; then
printf "clean (no shared resources in library %s)\n" "${library_id}"
else
printf "deleted %s of %s shared resources in library %s\n" \
"${deleted_total}" "${found_total}" "${library_id}"
fi
exit 0
fi

cleanup_resource() {
local resource=$1
local skip_name="${2:-}"
Expand Down
7 changes: 7 additions & 0 deletions .agents/skills/qa-engineer-manual/scripts/generate-asset.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ set -euo pipefail
# # To also copy the template PNG alongside:
# bash .claude/skills/qa-engineer-manual/scripts/generate-asset.sh \
# --filename "hero.png" --copy-png scenarios/my-scenario/assets/hero.png > scenarios/my-scenario/assets/hero.json
#
# For shared (org library) assets, keep the default `qa-` filename prefix so
# cleanup-remote.sh --shared can find them, and point --folder-id at the
# library (or a child folder):
# bash .claude/skills/qa-engineer-manual/scripts/generate-asset.sh \
# --filename "qa-hero.png" --folder-id <libraryId> \
# --copy-png .storyblok/assets/shared/<libraryId>/qa-hero.png > .storyblok/assets/shared/<libraryId>/qa-hero.json

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
template_png="${SCRIPT_DIR}/../templates/asset-template.png"
Expand Down
Loading
Loading