Beads are now a property of prayer steps, not separate steps

Each prayer in the library has an optional default_bead_type (small/large/
crucifix). Standard prayers get sensible defaults: Our Father=large,
Hail Mary=small, Sign of Cross=crucifix, Divine Mercy beads accordingly.

In the sequence, each step card shows a bead selector (—/○/●/✝) so users
can override the default per step. Adding a prayer pre-fills its default.

Bead library icon hints (○●✝) appear on prayer cards in the library.
Modal now includes a Bead selector for creating/editing prayers.

Remove the separate Bead Markers library section — beads live on prayers.
build_slides: prayer steps with bead_type now get a real bead_index so
the ring advances correctly during presentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 22:07:48 -07:00
parent 76a5061fba
commit 8c047f5b28
7 changed files with 228 additions and 159 deletions
+6 -3
View File
@@ -60,10 +60,12 @@ foreach ($steps as $i => $step) {
json_err("Invalid bead type on step " . ($i + 1));
}
} else {
$pid = (int)($step['prayer_id'] ?? 0);
$att = $step['attribution'] ?? 'leader_all';
$pid = (int)($step['prayer_id'] ?? 0);
$att = $step['attribution'] ?? 'leader_all';
$bt = $step['bead_type'] ?? null;
if (!$pid) json_err("Step " . ($i + 1) . " is missing a prayer");
if (!in_array($att, $valid_attributions)) json_err("Invalid attribution on step " . ($i + 1));
if ($bt !== null && !in_array($bt, $valid_bead_types)) json_err("Invalid bead type on step " . ($i + 1));
$prayer_ids[] = $pid;
}
}
@@ -131,7 +133,8 @@ try {
if ($type === 'bead') {
$step_stmt->execute([$session_id, 'bead', $step['bead_type'], $order, null, 'none']);
} else {
$step_stmt->execute([$session_id, 'prayer', null, $order, (int)$step['prayer_id'], $step['attribution']]);
$bt = in_array($step['bead_type'] ?? null, ['small','large','crucifix']) ? $step['bead_type'] : null;
$step_stmt->execute([$session_id, 'prayer', $bt, $order, (int)$step['prayer_id'], $step['attribution']]);
}
}
+23 -16
View File
@@ -61,7 +61,7 @@ if ($method === 'GET') {
$sql = "
SELECT cp.id, cp.name, cp.leader_text, cp.all_text,
cp.is_global, cp.created_by,
cp.default_bead_type, cp.is_global, cp.created_by,
u.role AS creator_role,
IF(cp.is_global=1 AND u.role='superadmin', 'standard',
IF(cp.is_global=1, 'global', 'mine')) AS source_tag
@@ -83,22 +83,25 @@ if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body) json_err('Invalid JSON');
$name = trim($body['name'] ?? '');
$leader = trim($body['leader_text'] ?? '');
$all = trim($body['all_text'] ?? '');
$global = $is_admin ? (int)!empty($body['is_global']) : 0;
$name = trim($body['name'] ?? '');
$leader = trim($body['leader_text'] ?? '');
$all = trim($body['all_text'] ?? '');
$global = $is_admin ? (int)!empty($body['is_global']) : 0;
$bead_type = in_array($body['default_bead_type'] ?? '', ['small','large','crucifix'])
? $body['default_bead_type'] : null;
if ($name === '') json_err('Prayer name is required');
$st = $pdo->prepare(
"INSERT INTO custom_prayers (name, leader_text, all_text, is_global, created_by)
VALUES (?, ?, ?, ?, ?)"
"INSERT INTO custom_prayers (name, leader_text, all_text, default_bead_type, is_global, created_by)
VALUES (?, ?, ?, ?, ?, ?)"
);
$st->execute([$name, $leader, $all, $global, $uid]);
$st->execute([$name, $leader, $all, $bead_type, $global, $uid]);
$new_id = (int)$pdo->lastInsertId();
$row = $pdo->prepare("
SELECT cp.id, cp.name, cp.leader_text, cp.all_text, cp.is_global, cp.created_by,
SELECT cp.id, cp.name, cp.leader_text, cp.all_text,
cp.default_bead_type, cp.is_global, cp.created_by,
u.role AS creator_role,
IF(cp.is_global=1 AND u.role='superadmin', 'standard',
IF(cp.is_global=1, 'global', 'mine')) AS source_tag
@@ -126,20 +129,24 @@ if ($method === 'PUT') {
$can_edit = $is_admin || (int)$prayer['created_by'] === $uid;
if (!$can_edit) json_err('Access denied', 403);
$name = trim($body['name'] ?? $prayer['name']);
$leader = trim($body['leader_text'] ?? $prayer['leader_text']);
$all = trim($body['all_text'] ?? $prayer['all_text']);
$global = $is_admin ? (int)!empty($body['is_global']) : (int)$prayer['is_global'];
$name = trim($body['name'] ?? $prayer['name']);
$leader = trim($body['leader_text'] ?? $prayer['leader_text']);
$all = trim($body['all_text'] ?? $prayer['all_text']);
$global = $is_admin ? (int)!empty($body['is_global']) : (int)$prayer['is_global'];
$bead_type = array_key_exists('default_bead_type', $body)
? (in_array($body['default_bead_type'], ['small','large','crucifix']) ? $body['default_bead_type'] : null)
: $prayer['default_bead_type'];
if ($name === '') json_err('Prayer name is required');
$pdo->prepare(
"UPDATE custom_prayers SET name=?, leader_text=?, all_text=?, is_global=?, updated_at=NOW()
"UPDATE custom_prayers SET name=?, leader_text=?, all_text=?, default_bead_type=?, is_global=?, updated_at=NOW()
WHERE id=?"
)->execute([$name, $leader, $all, $global, $id]);
)->execute([$name, $leader, $all, $bead_type, $global, $id]);
$row = $pdo->prepare("
SELECT cp.id, cp.name, cp.leader_text, cp.all_text, cp.is_global, cp.created_by,
SELECT cp.id, cp.name, cp.leader_text, cp.all_text,
cp.default_bead_type, cp.is_global, cp.created_by,
u.role AS creator_role,
IF(cp.is_global=1 AND u.role='superadmin', 'standard',
IF(cp.is_global=1, 'global', 'mine')) AS source_tag