diff --git a/assets/js/shared/tts-profile-store.js b/assets/js/shared/tts-profile-store.js new file mode 100644 index 000000000..268854662 --- /dev/null +++ b/assets/js/shared/tts-profile-store.js @@ -0,0 +1,160 @@ +const TEXT_TO_SPEECH_PROFILE_STORAGE_KEY = "gamefoundry.textToSpeech.profiles.v1"; +const TEXT_TO_SPEECH_PROFILE_STORE_VERSION = "tts-profile-store-v1"; + +const DEFAULT_LANGUAGE = "en-US"; +const DEFAULT_PROVIDER_KEY = "browser-speech"; +const DEFAULT_VOICE_AGE = "adult"; + +function clampNumber(value, fallback, min, max) { + const numberValue = Number(value); + if (!Number.isFinite(numberValue)) { + return fallback; + } + return Math.min(max, Math.max(min, numberValue)); +} + +function normalizedText(value, fallback = "") { + const text = String(value || "").trim(); + return text || fallback; +} + +function slugFromText(value, fallback = "item") { + return normalizedText(value, fallback) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") || fallback; +} + +function labelFromSlug(value, fallback = "Neutral") { + return normalizedText(value, fallback) + .replace(/[-_]+/g, " ") + .replace(/\b\w/g, (letter) => letter.toUpperCase()); +} + +function defaultStorage() { + try { + return typeof window === "undefined" ? null : window.localStorage; + } catch { + return null; + } +} + +function storagePayloadProfiles(payload) { + if (Array.isArray(payload)) { + return payload; + } + if (Array.isArray(payload?.profiles)) { + return payload.profiles; + } + return []; +} + +function normalizeSavedEmotion(emotion = {}) { + const emotionKey = slugFromText(emotion.emotion || emotion.id || emotion.emotionLabel, "neutral"); + const emotionLabel = normalizedText(emotion.emotionLabel || emotion.name, labelFromSlug(emotionKey)); + return { + active: emotion.active !== false, + emotion: emotionKey, + emotionLabel, + id: normalizedText(emotion.id, emotionKey), + messagePartsUsageCount: Math.max(0, Number(emotion.messagePartsUsageCount) || 0), + pitch: clampNumber(emotion.pitch, 1, 0.1, 2), + rate: clampNumber(emotion.rate, 1, 0.1, 2), + ssmlLikePreset: normalizedText(emotion.ssmlLikePreset, "normal"), + volume: clampNumber(emotion.volume, 1, 0, 1), + }; +} + +function normalizeSavedProfile(profile = {}) { + const name = normalizedText(profile.name, "Default Balanced Profile"); + const emotions = Array.isArray(profile.emotions) && profile.emotions.length + ? profile.emotions.map(normalizeSavedEmotion) + : [normalizeSavedEmotion()]; + return { + active: profile.active !== false, + age: normalizedText(profile.age, DEFAULT_VOICE_AGE), + emotions, + gender: normalizedText(profile.gender, "neutral"), + id: normalizedText(profile.id, slugFromText(name, "tts-profile")), + language: normalizedText(profile.language, DEFAULT_LANGUAGE), + messageStudioUsageCount: Math.max(0, Number(profile.messageStudioUsageCount) || 0), + name, + owner: "Audio", + providerKey: normalizedText(profile.providerKey, DEFAULT_PROVIDER_KEY), + voice: normalizedText(profile.voice), + voiceName: normalizedText(profile.voiceName || profile.voice, "Default browser voice"), + }; +} + +function normalizeSavedTextToSpeechProfiles(profiles = []) { + return Array.isArray(profiles) ? profiles.map(normalizeSavedProfile) : []; +} + +function readSavedTextToSpeechProfiles(storage = defaultStorage()) { + if (!storage || typeof storage.getItem !== "function") { + return []; + } + const raw = storage.getItem(TEXT_TO_SPEECH_PROFILE_STORAGE_KEY); + if (!raw) { + return []; + } + let payload; + try { + payload = JSON.parse(raw); + } catch { + throw new Error("Saved Text To Speech profiles could not be read."); + } + return normalizeSavedTextToSpeechProfiles(storagePayloadProfiles(payload)); +} + +function writeSavedTextToSpeechProfiles(profiles = [], storage = defaultStorage()) { + if (!storage || typeof storage.setItem !== "function") { + return false; + } + const payload = { + profiles: normalizeSavedTextToSpeechProfiles(profiles), + updatedAt: new Date().toISOString(), + version: TEXT_TO_SPEECH_PROFILE_STORE_VERSION, + }; + storage.setItem(TEXT_TO_SPEECH_PROFILE_STORAGE_KEY, JSON.stringify(payload)); + return true; +} + +function textToSpeechProfilesToMessageOptions(profiles = []) { + return normalizeSavedTextToSpeechProfiles(profiles) + .filter((profile) => profile.active !== false) + .map((profile) => ({ + active: true, + age: profile.age, + ageFilter: profile.age, + emotionSettings: profile.emotions + .filter((emotion) => emotion.active !== false) + .map((emotion) => ({ + active: true, + emotion: emotion.emotion, + emotionLabel: emotion.emotionLabel, + key: emotion.id, + pitch: emotion.pitch, + rate: emotion.rate, + ssmlLikePreset: emotion.ssmlLikePreset, + volume: emotion.volume, + })), + gender: profile.gender, + key: profile.id, + language: profile.language, + name: profile.name, + providerKey: profile.providerKey, + sourceProfileId: profile.id, + voice: profile.voice, + voiceName: profile.voiceName || profile.voice || "Default browser voice", + })); +} + +export { + TEXT_TO_SPEECH_PROFILE_STORAGE_KEY, + TEXT_TO_SPEECH_PROFILE_STORE_VERSION, + normalizeSavedTextToSpeechProfiles, + readSavedTextToSpeechProfiles, + textToSpeechProfilesToMessageOptions, + writeSavedTextToSpeechProfiles, +}; diff --git a/assets/toolbox/events/js/index.js b/assets/toolbox/events/js/index.js new file mode 100644 index 000000000..837198395 --- /dev/null +++ b/assets/toolbox/events/js/index.js @@ -0,0 +1,349 @@ +import { + requireServerApiData, + safeRequestServerApi, +} from "../../../../src/api/server-api-client.js"; + +const NEW_ROW_KEY = "__new__"; +const TABLE_COLSPAN = 5; +const EVENT_ACTION_OPTIONS = Object.freeze([ + Object.freeze({ key: "show-message", name: "Show Message", requiresMessage: true }), + Object.freeze({ key: "speak-message", name: "Speak Message", requiresMessage: true }), + Object.freeze({ key: "wait-for-continue", name: "Wait For Continue", requiresMessage: false }), +]); + +const elements = { + addAction: document.querySelector("[data-events-add-action]"), + log: document.querySelector("[data-events-log]"), + persistenceSource: document.querySelector("[data-events-persistence-source]"), + persistenceStorage: document.querySelector("[data-events-persistence-storage]"), + table: document.querySelector("[data-events-actions-table]"), + validationCard: document.querySelector("[data-events-validation-card]"), + validationErrors: document.querySelector("[data-events-validation-errors]"), +}; + +const state = { + editingKey: "", + eventActions: [], + messages: [], +}; + +function readData(path, context) { + return requireServerApiData(safeRequestServerApi(path), context); +} + +function writeData(path, body, context) { + return requireServerApiData( + safeRequestServerApi(path, { + body, + method: "POST", + }), + context, + ); +} + +function setText(element, value) { + if (element) { + element.textContent = value; + } +} + +function createCell(text) { + const cell = document.createElement("td"); + cell.textContent = text; + return cell; +} + +function createRowHeader(text) { + const cell = document.createElement("th"); + cell.scope = "row"; + cell.textContent = text; + return cell; +} + +function createInput(value, dataName, ariaLabel) { + const input = document.createElement("input"); + input.dataset[dataName] = ""; + input.type = "text"; + input.value = value || ""; + input.setAttribute("aria-label", ariaLabel); + return input; +} + +function createSelect(value, dataName, options, placeholder, ariaLabel) { + const select = document.createElement("select"); + select.dataset[dataName] = ""; + select.setAttribute("aria-label", ariaLabel); + const placeholderOption = document.createElement("option"); + placeholderOption.value = ""; + placeholderOption.textContent = placeholder; + select.append(placeholderOption); + options.forEach((optionValue) => { + const option = document.createElement("option"); + option.value = optionValue.key; + option.textContent = optionValue.name; + select.append(option); + }); + select.value = options.some((optionValue) => optionValue.key === value) ? value : ""; + return select; +} + +function createButton(label, dataName, value, options = {}) { + const button = document.createElement("button"); + button.className = options.primary ? "btn btn--compact primary" : "btn btn--compact"; + button.type = "button"; + button.dataset[dataName] = value; + button.textContent = label; + return button; +} + +function createActionGroup(...buttons) { + const group = document.createElement("div"); + group.className = "action-group action-group--tight"; + buttons.forEach((button) => group.append(button)); + return group; +} + +function tableMessage(text) { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = TABLE_COLSPAN; + cell.textContent = text; + row.append(cell); + return row; +} + +function formatUpdated(value) { + const date = new Date(value || ""); + if (Number.isNaN(date.getTime())) { + return "Unknown"; + } + return date.toLocaleString(undefined, { + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + month: "short", + year: "numeric", + }); +} + +function actionTypeDefinition(actionType) { + return EVENT_ACTION_OPTIONS.find((option) => option.key === actionType) || null; +} + +function activeMessages() { + return state.messages.filter((message) => message.active !== false); +} + +function clearValidation() { + elements.validationErrors?.replaceChildren(); + if (elements.validationCard) { + elements.validationCard.hidden = true; + } +} + +function showValidation(errors) { + if (!elements.validationCard || !elements.validationErrors) { + return; + } + elements.validationErrors.replaceChildren(); + errors.forEach((error) => { + const item = document.createElement("li"); + item.textContent = error; + elements.validationErrors.append(item); + }); + elements.validationCard.hidden = errors.length === 0; +} + +function showCreatorSafeFailure(message) { + const safeMessage = message || "Event action could not be saved. Check the Local API connection and try again."; + showValidation([safeMessage]); + setText(elements.log, safeMessage); +} + +function renderPersistence(persistence = {}) { + setText(elements.persistenceSource, "Local API"); + setText(elements.persistenceStorage, persistence.storage === "server-owned" ? "Local DB" : "Local DB"); +} + +function createEditRow(action = null) { + const key = action?.key || NEW_ROW_KEY; + const row = document.createElement("tr"); + row.dataset.eventsActionEditor = key; + + const nameCell = document.createElement("td"); + nameCell.append(createInput(action?.name || "", "eventActionName", "Action")); + + const typeCell = document.createElement("td"); + typeCell.append(createSelect(action?.actionType || "", "eventActionType", EVENT_ACTION_OPTIONS, "Select action", "Event option")); + + const messageCell = document.createElement("td"); + messageCell.append(createSelect(action?.messageKey || "", "eventActionMessage", activeMessages(), "Select message", "Message")); + + const actions = document.createElement("td"); + actions.append(createActionGroup( + createButton("Save", "eventsActionCommit", key, { primary: true }), + createButton("Cancel", "eventsActionCancel", key), + )); + + row.append( + nameCell, + typeCell, + messageCell, + createCell(action ? formatUpdated(action.updatedAt) : "New"), + actions, + ); + return row; +} + +function createActionRow(action) { + const row = document.createElement("tr"); + row.dataset.eventsActionRow = action.key; + const actions = document.createElement("td"); + actions.append(createActionGroup( + createButton("Edit", "eventsActionEdit", action.key), + )); + row.append( + createRowHeader(action.name), + createCell(action.actionLabel || actionTypeDefinition(action.actionType)?.name || "Unknown action"), + createCell(action.messageName || "No message required"), + createCell(formatUpdated(action.updatedAt)), + actions, + ); + return row; +} + +function renderRows() { + if (!elements.table) { + return; + } + elements.table.replaceChildren(); + if (state.editingKey === NEW_ROW_KEY) { + elements.table.append(createEditRow(null)); + } + if (!state.eventActions.length && state.editingKey !== NEW_ROW_KEY) { + elements.table.append(tableMessage("No event actions yet. Add an action when your event flow is ready.")); + return; + } + state.eventActions.forEach((action) => { + if (state.editingKey === action.key) { + elements.table.append(createEditRow(action)); + return; + } + elements.table.append(createActionRow(action)); + }); +} + +function editorValue(root, selector) { + return root?.querySelector(selector)?.value || ""; +} + +function actionValues(key) { + const root = elements.table?.querySelector(`[data-events-action-editor="${key}"]`); + const existing = state.eventActions.find((action) => action.key === key) || null; + return { + active: existing ? existing.active : true, + actionType: editorValue(root, "[data-event-action-type]"), + messageKey: editorValue(root, "[data-event-action-message]"), + name: editorValue(root, "[data-event-action-name]"), + }; +} + +function validateAction(values) { + const errors = []; + const actionDefinition = actionTypeDefinition(values.actionType); + if (!values.name.trim()) { + errors.push("Action is required."); + } + if (!actionDefinition) { + errors.push("Event option is required."); + } else if (actionDefinition.requiresMessage && !values.messageKey) { + errors.push("Message is required for this event option."); + } else if (!actionDefinition.requiresMessage && values.messageKey) { + errors.push("Message is not used by this event option."); + } + return errors; +} + +function loadAll() { + const messagePayload = readData("/messages/messages", "Messages list"); + const actionPayload = readData("/messages/event-actions", "Message event actions"); + state.messages = messagePayload.messages || []; + state.eventActions = actionPayload.eventActions || []; + renderPersistence(actionPayload.persistence || messagePayload.persistence); + renderRows(); + setText(elements.log, "Events loaded."); +} + +function reloadAfterChange() { + loadAll(); + renderRows(); +} + +function beginAddAction() { + clearValidation(); + state.editingKey = NEW_ROW_KEY; + renderRows(); + setText(elements.log, "Ready to add an event action."); +} + +function beginEditAction(key) { + clearValidation(); + state.editingKey = key; + renderRows(); + setText(elements.log, "Event action row opened inline."); +} + +function cancelEdit() { + state.editingKey = ""; + clearValidation(); + renderRows(); + setText(elements.log, "Event action edit canceled."); +} + +function commitAction(key) { + const values = actionValues(key); + const errors = validateAction(values); + if (errors.length) { + showValidation(errors); + setText(elements.log, "Event action needs required fields."); + return; + } + clearValidation(); + try { + const result = key === NEW_ROW_KEY + ? writeData("/messages/event-actions", values, "Create message event action") + : writeData(`/messages/event-actions/${encodeURIComponent(key)}`, values, "Update message event action"); + state.editingKey = ""; + reloadAfterChange(); + setText(elements.log, `Saved event action ${result.eventAction.name}.`); + } catch { + showCreatorSafeFailure("Event action was not saved. Check required fields and try again."); + } +} + +elements.addAction?.addEventListener("click", () => { + beginAddAction(); +}); + +elements.table?.addEventListener("click", (event) => { + const editButton = event.target.closest("[data-events-action-edit]"); + const commitButton = event.target.closest("[data-events-action-commit]"); + const cancelButton = event.target.closest("[data-events-action-cancel]"); + if (editButton) { + beginEditAction(editButton.dataset.eventsActionEdit); + return; + } + if (commitButton) { + commitAction(commitButton.dataset.eventsActionCommit); + return; + } + if (cancelButton) { + cancelEdit(); + } +}); + +try { + loadAll(); +} catch { + showCreatorSafeFailure("Events could not load message event actions. Start the Local API and reload this tool."); +} diff --git a/assets/toolbox/text-to-speech/js/index.js b/assets/toolbox/text-to-speech/js/index.js index 2d19193c2..77e090f4e 100644 --- a/assets/toolbox/text-to-speech/js/index.js +++ b/assets/toolbox/text-to-speech/js/index.js @@ -10,6 +10,10 @@ import { TEXT_TO_SPEECH_RANGE_DEFAULTS, TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS } from "../../../../src/engine/audio/TextToSpeechDefaults.js"; +import { + readSavedTextToSpeechProfiles, + writeSavedTextToSpeechProfiles, +} from "../../../js/shared/tts-profile-store.js"; const TTS_OWNERSHIP = Object.freeze({ DESIGN: "Design", @@ -88,7 +92,8 @@ const TTS_PROFILE_EMOTION_OPTIONS = Object.freeze([ Object.freeze({ label: "Calm", value: "calm" }), Object.freeze({ label: "Urgent", value: "urgent" }), Object.freeze({ label: "Whisper", value: "whisper" }), - Object.freeze({ label: "Excited", value: "excited" }) + Object.freeze({ label: "Excited", value: "excited" }), + Object.freeze({ label: "Robot", value: "robot" }) ]); function boundedNumber(value, { fallback, max, min, value: defaultValue }) { @@ -136,7 +141,7 @@ function createTtsMessage({ function createEmotionProfile({ key = "neutral", name = "Neutral", intensity = 0.5 } = {}) { const numericIntensity = Number(intensity); const safeIntensity = Number.isNaN(numericIntensity) ? 0.5 : Math.min(1, Math.max(0, numericIntensity)); - return { key: String(key), name: String(name), intensity: safeIntensity, owner: TTS_OWNERSHIP.DESIGN }; + return { key: String(key), name: String(name), intensity: safeIntensity, owner: TTS_OWNERSHIP.AUDIO }; } function createVoiceProfile({ key = "browser-speech", name = "Browser Speech", providerKey = "browser-speech", voiceId = "" } = {}) { @@ -145,7 +150,7 @@ function createVoiceProfile({ key = "browser-speech", name = "Browser Speech", p name: String(name), providerKey: String(providerKey), voiceId: String(voiceId), - owner: TTS_OWNERSHIP.DESIGN, + owner: TTS_OWNERSHIP.AUDIO, generatedAudioOwner: TTS_OWNERSHIP.AUDIO }; } @@ -279,29 +284,6 @@ function createDefaultTextToSpeechProfiles(voiceOptions = []) { ]; } -function createMessageStudioTtsProfileOptions(profiles = []) { - return profiles - .filter((profile) => profile?.active !== false) - .map((profile) => ({ - active: true, - emotionSettings: Array.isArray(profile.emotions) - ? profile.emotions.filter((emotion) => emotion.active !== false).map((emotion) => ({ - emotion: emotion.emotion, - emotionLabel: emotion.emotionLabel, - pitch: emotion.pitch, - rate: emotion.rate, - ssmlLikePreset: emotion.ssmlLikePreset, - volume: emotion.volume - })) - : [], - key: profile.id, - language: profile.language, - name: profile.name, - providerKey: profile.providerKey || "browser-speech", - voiceName: profile.voiceName || profile.voice || "" - })); -} - function createSpeechPreviewRequest({ pitch = TEXT_TO_SPEECH_DEFAULTS.pitch, rate = TEXT_TO_SPEECH_DEFAULTS.rate, @@ -479,7 +461,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const input = document.createElement("input"); const range = TEXT_TO_SPEECH_RANGE_DEFAULTS[kind] || TEXT_TO_SPEECH_RANGE_DEFAULTS.rate; input.dataset[dataName] = ""; - input.type = "number"; + input.type = "range"; input.min = String(range.min); input.max = String(range.max); input.step = String(range.step); @@ -516,10 +498,14 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech return select; } - function voiceSelectOptions() { - return state.voiceOptions.length + function voiceSelectOptions(currentValue = "") { + const options = state.voiceOptions.length ? state.voiceOptions.map((option) => ({ label: option.label, value: option.value })) : [{ label: "No browser voices available", value: "" }]; + if (currentValue && !options.some((option) => String(option.value) === String(currentValue))) { + return [{ label: profileVoiceName({ voice: currentValue, voiceName: currentValue }), value: currentValue }, ...options]; + } + return options; } function languageSelectOptions() { @@ -558,7 +544,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech nameCell.setAttribute("role", "button"); nameCell.tabIndex = 0; nameCell.setAttribute("aria-expanded", String(state.selectedProfileId === profile.id)); - nameCell.title = "Open Emotion Settings"; + nameCell.title = "Open emotions"; nameCell.textContent = `${state.selectedProfileId === profile.id ? "v" : ">"} ${profile.name}`; const deleteButton = createButton("Delete", "ttsDeleteProfile", profile.id); if (profileInUseByMessageStudio(profile)) { @@ -571,9 +557,9 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ); row.append( nameCell, + createCell(labelForOption(TTS_PROFILE_GENDER_OPTIONS, profile.gender, "Neutral")), createCell(profileVoiceName(profile)), createCell(profile.language), - createCell(labelForOption(TTS_PROFILE_GENDER_OPTIONS, profile.gender, "Neutral")), createCell(labelForOption(TEXT_TO_SPEECH_AGE_FILTER_OPTIONS, profile.age, "Any")), createCell(String(profile.emotions.length)), createCell(profile.active ? "Active" : "Inactive"), @@ -613,12 +599,12 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const nameCell = document.createElement("td"); nameCell.append(createTextInput(profile?.name || "", "ttsProfileName")); + const genderCell = document.createElement("td"); + genderCell.append(createEditorSelect(profile?.gender || "neutral", "ttsProfileGender", TTS_PROFILE_GENDER_OPTIONS)); const voiceCell = document.createElement("td"); - voiceCell.append(createEditorSelect(profile?.voice || "", "ttsProfileVoice", voiceSelectOptions(), "Select voice")); + voiceCell.append(createEditorSelect(profile?.voice || "", "ttsProfileVoice", voiceSelectOptions(profile?.voice || ""), "Select voice")); const languageCell = document.createElement("td"); languageCell.append(createEditorSelect(profile?.language || TEXT_TO_SPEECH_DEFAULTS.language, "ttsProfileLanguage", languageSelectOptions())); - const genderCell = document.createElement("td"); - genderCell.append(createEditorSelect(profile?.gender || "neutral", "ttsProfileGender", TTS_PROFILE_GENDER_OPTIONS)); const ageCell = document.createElement("td"); ageCell.append(createEditorSelect(profile?.age || TEXT_TO_SPEECH_DEFAULTS.voiceAge, "ttsProfileAge", TEXT_TO_SPEECH_AGE_FILTER_OPTIONS)); const emotionCountCell = createCell(profile ? String(profile.emotions.length) : "1"); @@ -630,7 +616,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech createButton("Cancel", "ttsCancelProfile", key), )); - row.append(nameCell, voiceCell, languageCell, genderCell, ageCell, emotionCountCell, statusCell, actionsCell); + row.append(nameCell, genderCell, voiceCell, languageCell, ageCell, emotionCountCell, statusCell, actionsCell); return row; } @@ -649,21 +635,15 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const profile = state.profiles.find((candidate) => candidate.id === profileId); const wrapper = document.createElement("div"); wrapper.className = "content-stack"; - const context = document.createElement("div"); - context.className = "kicker"; - context.textContent = "TTS Profile / Emotion Settings"; - const heading = document.createElement("h3"); - heading.textContent = "Emotion Settings"; - wrapper.append(context, heading); const tableWrapper = document.createElement("div"); tableWrapper.className = "table-wrapper"; const table = document.createElement("table"); table.className = "data-table"; - table.setAttribute("aria-label", "Emotion Settings"); + table.setAttribute("aria-label", "TTS Profile Emotions"); const thead = document.createElement("thead"); const headerRow = document.createElement("tr"); - ["Emotion", "Pitch", "Rate", "Volume", "Delivery Preset", "Status", "Actions"].forEach((label) => { + ["Emotion", "Pitch", "Rate", "Volume", "Actions"].forEach((label) => { const header = document.createElement("th"); header.scope = "col"; header.textContent = label; @@ -674,7 +654,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech tbody.dataset.ttsEmotionTable = profileId; if (!profile?.emotions.length && state.editingEmotionId !== NEW_ROW_KEY) { - tbody.append(tableMessage(7, "No emotion settings for this profile.")); + tbody.append(tableMessage(5, "No emotions for this profile.")); } profile?.emotions.forEach((emotion) => { @@ -687,11 +667,12 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const deleteButton = createButton("Delete", "ttsDeleteEmotion", emotion.id); if (emotionInUseByMessageParts(emotion)) { deleteButton.disabled = true; - deleteButton.title = "Delete disabled: emotion is in use by Message Parts."; + deleteButton.title = "Delete disabled: emotion is in use by sentences."; } const actions = createActionGroup( createButton("Edit Emotion", "ttsEditEmotion", emotion.id), deleteButton, + createButton("Play", "ttsPlayEmotion", emotion.id), ); const actionsCell = document.createElement("td"); actionsCell.append(actions); @@ -700,8 +681,6 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech createCell(String(emotion.pitch)), createCell(String(emotion.rate)), createCell(String(emotion.volume)), - createCell(labelForOption(TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS, emotion.ssmlLikePreset, "Normal")), - createCell(emotion.active ? "Active" : "Inactive"), actionsCell, ); tbody.append(row); @@ -721,7 +700,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech } function createEmotionAddControlRow(profileId) { - const row = tableActionRow(7, createButton("Add Emotion", "ttsAddEmotion", profileId)); + const row = tableActionRow(5, createButton("Add Emotion", "ttsAddEmotion", profileId)); row.dataset.ttsEmotionAddControlRow = profileId; return row; } @@ -738,16 +717,12 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech rateCell.append(createNumberInput(emotion?.rate ?? 1, "ttsEmotionRate", "rate")); const volumeCell = document.createElement("td"); volumeCell.append(createNumberInput(emotion?.volume ?? 1, "ttsEmotionVolume", "volume")); - const presetCell = document.createElement("td"); - presetCell.append(createEditorSelect(emotion?.ssmlLikePreset || "normal", "ttsEmotionSsmlPreset", TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS)); - const statusCell = document.createElement("td"); - statusCell.append(createCheckbox(emotion?.active !== false, "ttsEmotionActive")); const actionsCell = document.createElement("td"); actionsCell.append(createActionGroup( createButton("Save", "ttsCommitEmotion", key), createButton("Cancel", "ttsCancelEmotion", key), )); - row.append(emotionCell, pitchCell, rateCell, volumeCell, presetCell, statusCell, actionsCell); + row.append(emotionCell, pitchCell, rateCell, volumeCell, actionsCell); return row; } @@ -786,22 +761,31 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech return errors; } + function persistProfilesForMessages() { + try { + writeSavedTextToSpeechProfiles(state.profiles); + } catch { + writeStatus("Text To Speech profiles were saved for this tool but could not be shared with Messages. Try again after refreshing the browser.", "FAIL"); + } + } + function emotionValues(key) { const row = elements.profileTable?.querySelector(`[data-tts-emotion-editor="${key}"]`); + const existing = selectedProfile()?.emotions.find((emotion) => emotion.id === key) || null; return createTextToSpeechProfileEmotion({ - active: editorChecked(row, "[data-tts-emotion-active]"), + active: existing?.active !== false, emotion: editorValue(row, "[data-tts-emotion-name]"), id: key === NEW_ROW_KEY ? "" : key, pitch: editorValue(row, "[data-tts-emotion-pitch]"), rate: editorValue(row, "[data-tts-emotion-rate]"), - ssmlLikePreset: editorValue(row, "[data-tts-emotion-ssml-preset]"), + ssmlLikePreset: existing?.ssmlLikePreset || "normal", volume: editorValue(row, "[data-tts-emotion-volume]"), }); } function validateEmotion(emotion, existingId = "") { const errors = []; - if (!state.selectedProfileId) errors.push("Select a TTS Profile before adding Emotion Settings."); + if (!state.selectedProfileId) errors.push("Select a TTS Profile before adding an emotion."); if (!emotion.emotion) errors.push("Emotion is required."); const profile = selectedProfile(); if (profile?.emotions.some((candidate) => candidate.id !== existingId && candidate.emotion === emotion.emotion)) { @@ -811,6 +795,9 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech } function addProfile() { + if (engine.isSupported()) { + refreshVoices(); + } state.editingProfileId = NEW_ROW_KEY; state.editingEmotionId = ""; state.selectedProfileId = ""; @@ -839,6 +826,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech state.selectedProfileId = profile.id; state.selectedEmotionId = previewEmotion(profile)?.id || ""; state.editingProfileId = ""; + persistProfilesForMessages(); renderProfileRows(); refreshActionState(); writeStatus(`Saved TTS profile: ${profile.name}.`); @@ -854,6 +842,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech state.profiles = state.profiles.filter((candidate) => candidate.id !== key); if (state.selectedProfileId === key) state.selectedProfileId = state.profiles[0]?.id || ""; if (!state.selectedProfileId) state.selectedEmotionId = ""; + persistProfilesForMessages(); renderProfileRows(); refreshActionState(); writeStatus(`Deleted TTS profile: ${profile.name}.`); @@ -864,14 +853,14 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech state.editingProfileId = ""; state.editingEmotionId = NEW_ROW_KEY; renderProfileRows(); - writeStatus("Ready to add an emotion setting."); + writeStatus("Ready to add an emotion."); } function commitEmotion(key) { const emotion = emotionValues(key); const errors = validateEmotion(emotion, key === NEW_ROW_KEY ? "" : key); if (errors.length) { - writeStatus(`Emotion setting save blocked: ${errors.join(" ")}`, "FAIL"); + writeStatus(`Emotion save blocked: ${errors.join(" ")}`, "FAIL"); return; } const profile = selectedProfile(); @@ -888,9 +877,10 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech } state.editingEmotionId = ""; state.selectedEmotionId = emotion.id; + persistProfilesForMessages(); renderProfileRows(); refreshActionState(); - writeStatus(`Saved emotion setting: ${emotion.emotionLabel}.`); + writeStatus(`Saved emotion: ${emotion.emotionLabel}.`); } function deleteEmotion(key) { @@ -898,14 +888,15 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const emotion = profile?.emotions.find((candidate) => candidate.id === key); if (!profile || !emotion) return; if (emotionInUseByMessageParts(emotion)) { - writeStatus(`Delete emotion disabled: ${emotion.emotionLabel} is in use by Message Parts.`, "FAIL"); + writeStatus(`Delete emotion disabled: ${emotion.emotionLabel} is in use by sentences.`, "FAIL"); return; } profile.emotions = profile.emotions.filter((candidate) => candidate.id !== key); if (state.selectedEmotionId === key) state.selectedEmotionId = previewEmotion(profile)?.id || ""; + persistProfilesForMessages(); renderProfileRows(); refreshActionState(); - writeStatus(`Deleted emotion setting: ${emotion.emotionLabel}.`); + writeStatus(`Deleted emotion: ${emotion.emotionLabel}.`); } function selectProfile(profileId) { @@ -916,7 +907,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech renderProfileRows(); refreshActionState(); if (state.selectedProfileId) { - writeStatus(`Opened Emotion Settings for ${selectedProfile()?.name}.`); + writeStatus(`Opened emotions for ${selectedProfile()?.name}.`); } } @@ -935,9 +926,23 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech if (state.profiles.length) { return; } + try { + const savedProfiles = readSavedTextToSpeechProfiles(); + if (savedProfiles.length) { + state.profiles = savedProfiles; + state.selectedProfileId = ""; + state.selectedEmotionId = ""; + renderProfileRows(); + refreshActionState(); + return; + } + } catch { + writeStatus("Saved Text To Speech profiles could not be loaded. Default profiles are available for this session.", "FAIL"); + } state.profiles = createDefaultTextToSpeechProfiles(state.voiceOptions); state.selectedProfileId = ""; state.selectedEmotionId = ""; + persistProfilesForMessages(); renderProfileRows(); refreshActionState(); } @@ -975,15 +980,18 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech return url.href; } - function speak() { - const profile = previewProfile(); - const emotion = previewEmotion(profile); + function speakEmotion(profile, emotion) { + if (!engine.isSupported()) { + writeStatus("SpeechSynthesis is unavailable in this browser. Use a browser with Web Speech API support.", "FAIL"); + refreshActionState(); + return; + } if (!profile) { writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select a TTS Profile first.`, "FAIL"); return; } if (!emotion) { - writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select an Emotion Setting first.`, "FAIL"); + writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select an emotion first.`, "FAIL"); return; } const request = createSpeechPreviewRequest({ @@ -1001,14 +1009,14 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech return; } const result = engine.speak({ - language: profile.language, + language: request.language, pitch: request.pitch, rate: request.rate, speechItemId: `${profile.id}:${emotion.id}:preview`, speechItemName: previewSpeechLabel(profile, emotion), ssmlLikePreset: emotion.ssmlLikePreset, text: request.text, - voice: profile.voice, + voice: request.voice, voiceAge: profile.age, volume: request.volume, }); @@ -1022,6 +1030,11 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech refreshActionState(); } + function speak() { + const profile = previewProfile(); + speakEmotion(profile, previewEmotion(profile)); + } + function pause() { const result = engine.pause(); if (!result.ok) { @@ -1057,6 +1070,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech const commitEmotionButton = event.target.closest("[data-tts-commit-emotion]"); const cancelEmotionButton = event.target.closest("[data-tts-cancel-emotion]"); const deleteEmotionButton = event.target.closest("[data-tts-delete-emotion]"); + const playEmotionButton = event.target.closest("[data-tts-play-emotion]"); const deleteProfileButton = event.target.closest("[data-tts-delete-profile]"); const emotionRow = event.target.closest("[data-tts-emotion-row]"); const editEmotionButton = event.target.closest("[data-tts-edit-emotion]"); @@ -1080,6 +1094,9 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech return; } if (editProfileButton) { + if (engine.isSupported()) { + refreshVoices(); + } state.editingProfileId = editProfileButton.dataset.ttsEditProfile; state.selectedProfileId = editProfileButton.dataset.ttsEditProfile; state.selectedEmotionId = previewEmotion(selectedProfile())?.id || ""; @@ -1103,24 +1120,31 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech if (cancelEmotionButton) { state.editingEmotionId = ""; renderProfileRows(); - writeStatus("Emotion setting edit canceled."); + writeStatus("Emotion edit canceled."); return; } if (editEmotionButton) { state.editingEmotionId = editEmotionButton.dataset.ttsEditEmotion; state.selectedEmotionId = editEmotionButton.dataset.ttsEditEmotion; renderProfileRows(); - writeStatus("Emotion setting opened inline."); + writeStatus("Emotion opened inline."); return; } if (deleteEmotionButton) { deleteEmotion(deleteEmotionButton.dataset.ttsDeleteEmotion); return; } + if (playEmotionButton) { + const profile = selectedProfile(); + const emotion = profile?.emotions.find((candidate) => candidate.id === playEmotionButton.dataset.ttsPlayEmotion) || null; + state.selectedEmotionId = playEmotionButton.dataset.ttsPlayEmotion; + speakEmotion(profile, emotion); + return; + } if (emotionRow) { state.selectedEmotionId = emotionRow.dataset.ttsEmotionRow; refreshActionState(); - writeStatus(`Selected Emotion Setting: ${previewEmotion()?.emotionLabel || "Unknown"}.`); + writeStatus(`Selected emotion: ${previewEmotion()?.emotionLabel || "Unknown"}.`); return; } if (profileNameCell) { @@ -1189,7 +1213,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech }; } -if (typeof document !== "undefined") { +if (typeof document !== "undefined" && document.querySelector("[data-tts-profile-table]")) { initializeTextToSpeechTool(document); } @@ -1201,7 +1225,6 @@ export { TTS_PROVIDER_ADAPTER_PLAN, createEmotionProfile, createDefaultTextToSpeechProfiles, - createMessageStudioTtsProfileOptions, createSpeechPreviewRequest, createTextToSpeechProfile, createTextToSpeechProfileEmotion, diff --git a/docs_build/dev/reports/BRAVO_EOD_2026-06-23.md b/docs_build/dev/reports/BRAVO_EOD_2026-06-23.md new file mode 100644 index 000000000..fba8d2b91 --- /dev/null +++ b/docs_build/dev/reports/BRAVO_EOD_2026-06-23.md @@ -0,0 +1,53 @@ +# BRAVO EOD Report - 2026-06-23 + +## Branch Status +- Current Bravo branch: `team/BRAVO/messages` +- Latest implementation commit hash: `7415046cd65c53b24ba0b68aaf38fcf4b4c72517` +- Latest implementation commit subject: `PR_26174_BRAVO_messages_tts_stack` +- Commits relative to `origin/main` at implementation commit: ahead `1`, behind `7` +- Merge status: Not merged to `main` +- Branch kept active: Yes +- Local unstaged non-Bravo change intentionally not committed: `.gitignore` adds `docs_build/dev/ProjectInstructions.zip` + +## Git Status Summary +- Bravo implementation stack committed locally. +- EOD report generated under `docs_build/dev/reports/`. +- Push to GitHub was attempted and blocked by non-interactive authentication. + +## Open Bravo PR List +- No open Bravo PRs were found in `ToolboxAid/HTML-JavaScript-Gaming` through the GitHub connector search. +- PR creation was not completed because the Bravo branch could not be pushed to GitHub. + +## Validation PASS/FAIL Summary +- PASS: `node tests/tools/Text2SpeechShell.test.mjs` +- PASS: `node tests/tools/MessagesPlaybackSource.test.mjs` +- PASS: `node tests/dev-runtime/MessagesPublishValidation.test.mjs` +- PASS: `node --test-name-pattern "Messages Local API seeds" tests/dev-runtime/DbSeedIntegrity.test.mjs` +- PASS: `npm run test:playwright:static` +- PASS: `git diff --check` on Bravo source/test/report files +- FAIL/BLOCKED: `npx playwright install chromium` + - Timed out after five minutes. +- FAIL/BLOCKED: Browser Playwright validation + - Local Chromium executable is unavailable. + +## Push / PR Status +- FAIL: `git push -u origin team/BRAVO/messages` + - Initial attempts timed out. + - Trace retry returned GitHub `401 Unauthorized`. + - Git Credential Manager could not prompt in non-interactive execution. +- FAIL: GitHub CLI workflow + - `gh` is not installed in this environment. +- FAIL: PR creation/update + - No remote Bravo branch exists, so the PR stack could not be created or updated. + +## Known Unresolved Issues +- Parent Message Play validation incomplete. +- Child Sentence Play still showing: `Select an available browser voice before preview.` +- Messages not confirmed to use active Text To Speech profile data. +- PR_021 architecture review failed. + +## Required Follow-Up +- Provide non-interactive GitHub credentials or install/authenticate `gh`. +- Re-run `git push -u origin team/BRAVO/messages`. +- Create/update the Bravo PR stack after the remote branch exists. +- Install Playwright Chromium and re-run the browser Messages validation. diff --git a/docs_build/dev/reports/PR_26174_BRAVO_001-messages-table-structure.md b/docs_build/dev/reports/PR_26174_BRAVO_001-messages-table-structure.md new file mode 100644 index 000000000..d2242acf3 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_001-messages-table-structure.md @@ -0,0 +1,65 @@ +# PR_26174_BRAVO_001-messages-table-structure + +## Summary + +Converted Message Studio to the approved table-first Messages surface with server-owned persistence, inline row editing, and guarded message deletion. The visible table now uses Theme V2 table classes and exposes Message, Text, Tags, Emotion, Voice, Updated, and Actions columns. + +## Branch Validation + +| Check | Result | Notes | +| --- | --- | --- | +| Current branch is main | PASS | `git branch --show-current` returned `main`. | + +## Requirement Checklist + +| Requirement | Result | Notes | +| --- | --- | --- | +| Convert existing Messages tool/page to approved GFS table-first layout | PASS | `toolbox/messages/index.html` and `toolbox/messages/messages.js` now render the Messages table as the primary center surface. | +| Columns: Message, Text, Tags, Emotion, Voice, Updated, Actions | PASS | Static table headers match the requested seven-column structure. | +| Add Message button above/near table | PASS | Added a Theme V2 button in the table surface header. | +| Row actions: Edit, Delete | PASS | Saved rows render Edit and Delete only. | +| Edit mode actions: Save, Cancel | PASS | Inline editor rows render Save and Cancel only. | +| Delete blocked/disabled when referenced | PASS | UI disables Delete when message parts reference the message; service delete path returns 409 for referenced rows. | +| Use Theme V2 and existing reusable table patterns | PASS | Reused `surface-header`, `action-group`, `table-wrapper`, and `data-table`; no page-local CSS added. | +| No inline styles, style blocks, inline handlers, or script blocks in HTML | PASS | Static PCRE check returned no matches for inline style/script/handler patterns. | +| No browser-owned authoritative product data | PASS | Messages, emotions, and reference counts load through the Local API; no localStorage/product-data fallback added. | +| No TTS implementation in this PR | PASS | Removed visible playback/TTS controls from Message Studio and did not add TTS behavior. Existing server TTS endpoints were not expanded. | +| No Emotion Profiles or Voice Profiles tools yet | PASS | Emotion/Voice remain visible reference columns/placeholders only; no profile management UI added. | +| Creator-safe empty/error states | PASS | Empty table and runtime failure messages avoid server diagnostics and stack details. | +| Required repo-structured ZIP under tmp/ | PASS | Prepared for `tmp/PR_26174_BRAVO_001-messages-table-structure_delta.zip`. | + +## Validation Lane Report + +| Lane | Result | Command / Notes | +| --- | --- | --- | +| Branch check | PASS | `git branch --show-current` -> `main`. | +| JS syntax | PASS | `node --check` on touched JS/MJS files. | +| Messages service contract | PASS | Inline Node check verified create, unreferenced delete, and referenced delete guard using `createMessagesPostgresClientStub`. | +| HTML restriction static check | PASS | `rg -n --pcre2 ']*\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Diff whitespace | PASS | `git diff --check -- ` passed with line-ending warnings only. | +| Dependency install for validation | PASS_WITH_NOTE | `npm ci` completed; npm reported one existing high-severity audit finding, not changed in this PR. | +| Targeted Playwright Messages spec | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs` could not launch because Chromium was missing. `npx playwright install chromium` timed out twice (about 184s and 364s). A temporary system-Chrome config using `C:\Program Files\Google\Chrome\Application\chrome.exe` also timed out after about 184s, so browser assertions did not complete. | + +## Manual Validation Notes + +- Static source inspection confirms the Messages table headers are Message, Text, Tags, Emotion, Voice, Updated, Actions. +- Static source inspection confirms Add Message, Edit/Delete, Save/Cancel paths are present in the Messages controller. +- Static source inspection confirms old visible TTS Profile, Play Message, Stop Playback, and TTS selector hooks are absent from `toolbox/messages/index.html` and `toolbox/messages/messages.js`. +- Manual browser validation was not completed because the Playwright Chromium binary could not be installed and the fallback system-Chrome run timed out within the available command window. +- Unrelated pre-existing `.gitignore` modification was left untouched and excluded from this PR delta. + +## Changed Files + +- `src/dev-runtime/messages/messages-postgres-service.mjs` +- `tests/helpers/messagesPostgresClientStub.mjs` +- `tests/playwright/tools/MessagesTool.spec.mjs` +- `toolbox/messages/index.html` +- `toolbox/messages/messages-api-client.js` +- `toolbox/messages/messages.js` +- `docs_build/dev/reports/codex_review.diff` +- `docs_build/dev/reports/codex_changed_files.txt` +- `docs_build/dev/reports/PR_26174_BRAVO_001-messages-table-structure.md` + +## ZIP + +- `tmp/PR_26174_BRAVO_001-messages-table-structure_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_002-message-parts-child-table.md b/docs_build/dev/reports/PR_26174_BRAVO_002-message-parts-child-table.md new file mode 100644 index 000000000..8e761d4c1 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_002-message-parts-child-table.md @@ -0,0 +1,60 @@ +# PR_26174_BRAVO_002-message-parts-child-table + +## Summary + +Added expandable Message Parts child rows under each parent Message row. The child surface uses the existing Theme V2 table pattern with Order, Text, Emotion, Voice, and Actions columns, and supports Add Part, Edit, Save, Cancel, and Delete through the Local API. + +## Branch Validation + +| Check | Result | Notes | +| --- | --- | --- | +| Current branch is Bravo work branch | PASS | `git branch --show-current` returned `team/BRAVO/messages`. | +| Did not return to main | PASS | All PR_002 work was performed on `team/BRAVO/messages`. | + +## Requirement Checklist + +| Requirement | Result | Notes | +| --- | --- | --- | +| Add expandable child rows to Messages | PASS | Parent rows expose a Parts action that expands a child host row. | +| Parent row remains Message | PASS | The parent table remains Message-first with PR_001 columns. | +| Child rows are Message Parts | PASS | Expanded host renders Message Part rows from `messages_segments`. | +| Child columns Order, Text, Emotion, Voice, Actions | PASS | Child table renders exactly those headers. | +| Support Add Part, Edit, Save, Cancel, Delete | PASS | Inline child table actions are wired through external JS and Local API calls. | +| Reuse approved table-child-surface pattern | PASS | Reused `content-stack`, `surface-header`, `table-wrapper`, and `data-table data-table--fixed`. | +| Do not implement TTS playback yet | PASS | Voice remains a visible placeholder; no playback controls or speech calls were added. | +| No browser-owned authoritative product data | PASS | Message Parts are read/written/deleted through the Local API service. | +| Server/API owns authoritative key generation | PASS | Segment create still uses server-generated ULID keys. | +| Creator-safe errors only | PASS | UI save/delete failures use Creator-safe copy without server details. | +| Delete blocked when records are referenced | PASS | Parent message delete remains disabled when child parts exist; segment delete has no downstream references in this PR. | + +## Validation Lane Report + +| Lane | Result | Command / Notes | +| --- | --- | --- | +| JS syntax | PASS | `node --check toolbox/messages/messages.js; node --check toolbox/messages/messages-api-client.js; node --check src/dev-runtime/messages/messages-postgres-service.mjs; node --check tests/playwright/tools/MessagesTool.spec.mjs`. | +| Message Parts Local API contract | PASS | Inline Node check verified part create, parent delete block while referenced, part update, part delete, and parent delete unblock. | +| HTML restriction static check | PASS | `rg -n --pcre2 ']*\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Diff whitespace | PASS | `git diff --check -- ` passed with line-ending warnings only. | +| Targeted Playwright | BLOCKED | Browser execution remains blocked because Playwright Chromium is missing in this workspace. | +| Fallback `npm run test:workspace-v2` | BLOCKED | The fallback lane also invokes Playwright and failed at browser launch with missing Chromium. | + +## Manual Validation Notes + +- Static inspection confirms no inline styles, style blocks, inline handlers, or HTML script blocks were added. +- Static inspection confirms PR_002 did not add TTS playback or provider runtime behavior. +- The parent delete guard now updates naturally after child part deletion because reference counts are reloaded from the Local API. +- Browser validation could not complete in this environment due missing Playwright Chromium. + +## Changed Files + +- `src/dev-runtime/messages/messages-postgres-service.mjs` +- `tests/playwright/tools/MessagesTool.spec.mjs` +- `toolbox/messages/messages-api-client.js` +- `toolbox/messages/messages.js` +- `docs_build/dev/reports/codex_review.diff` +- `docs_build/dev/reports/codex_changed_files.txt` +- `docs_build/dev/reports/PR_26174_BRAVO_002-message-parts-child-table.md` + +## ZIP + +- `tmp/PR_26174_BRAVO_002-message-parts-child-table_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_003-emotion-profiles-table.md b/docs_build/dev/reports/PR_26174_BRAVO_003-emotion-profiles-table.md new file mode 100644 index 000000000..d885a26a8 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_003-emotion-profiles-table.md @@ -0,0 +1,54 @@ +# PR_26174_BRAVO_003-emotion-profiles-table + +## Scope +- Added an Emotion Profiles reusable-asset table to Message Studio. +- Added inline Add/Edit/Save/Cancel behavior for emotion profiles through the Local API. +- Kept Messages and Message Parts referencing Emotion Profiles by profile key. +- Tightened emotion-profile rate/pitch/volume validation so invalid numeric inputs fail instead of falling back. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_003 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Add Emotion Profiles as reusable assets | PASS | `toolbox/messages/index.html` adds the Emotion Profiles table and Add Emotion action; `toolbox/messages/messages.js` creates/updates profiles through the Local API. | +| Table columns: Emotion, Rate, Pitch, Volume, Updated, Actions | PASS | Emotion Profiles table headers match the required column list. | +| Seed/display Calm, Urgent, Whisper, Excited, Angry | PASS | Existing Local API seed data includes those required starter examples; the Playwright spec asserts their display. | +| Messages reference Emotion Profiles | PASS | Message rows continue to store/select `emotionProfileKey`; no rate/pitch/volume fields were added to messages. | +| Message Parts reference Emotion Profiles | PASS | Message Part rows continue to store/select `emotionProfileKey`; no rate/pitch/volume fields were added to parts. | +| Do not duplicate rate/pitch/volume directly on messages | PASS | Service contract validation asserts messages and parts do not expose `rate`, `pitch`, or `volume`. | +| Theme V2 and reusable table patterns | PASS | Uses `card`, `surface-header`, `table-wrapper`, `data-table`, `action-group`, and `btn`. | +| No inline styles, style blocks, inline handlers, or inline script blocks | PASS | Static scan found no disallowed inline HTML patterns in `toolbox/messages/index.html`. | +| No browser-owned authoritative product data | PASS | Profile creation and updates go through the Local API; keys remain server/API owned. | +| No TTS implementation in this PR | PASS | No playback/runtime/provider behavior was added. | +| No Emotion Profiles expansion beyond this PR purpose | PASS | Added only the requested profile table and profile references. | +| Creator-safe empty/error states | PASS | Empty table and failure messages avoid server detail exposure. | +| Delete blocked when referenced | PASS | No new direct delete path was introduced for emotion profiles; existing message delete guard remains intact. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check toolbox/messages/messages.js; node --check src/dev-runtime/messages/messages-postgres-service.mjs; node --check tests/playwright/tools/MessagesTool.spec.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Local API contract | PASS | Inline Node probe created/edited an emotion profile, rejected invalid numeric input, verified starter examples, and verified messages/parts only reference profile keys. | +| Diff hygiene | PASS | `git diff --check -- toolbox/messages/index.html toolbox/messages/messages.js src/dev-runtime/messages/messages-postgres-service.mjs tests/playwright/tools/MessagesTool.spec.mjs` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --grep "Message Studio uses the approved table-first Messages structure" --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- The impacted UI path was covered by static DOM checks, focused spec updates, and a Local API service contract probe. +- Generated fallback lane side reports were restored; only PR_003-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_003-emotion-profiles-table.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_003-emotion-profiles-table_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_004-voice-profiles-table.md b/docs_build/dev/reports/PR_26174_BRAVO_004-voice-profiles-table.md new file mode 100644 index 000000000..7af2dec01 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_004-voice-profiles-table.md @@ -0,0 +1,55 @@ +# PR_26174_BRAVO_004-voice-profiles-table + +## Scope +- Added a Voice Profiles reusable-asset table to Message Studio. +- Seeded the required starter voice examples: Narrator, Hero, Merchant, Robot, Monster. +- Added server/API-owned `voiceProfileKey` references on Messages and Message Parts. +- Replaced visible Voice placeholders with profile selects while keeping playback/TTS runtime out of scope. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_004 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Add Voice Profiles as reusable assets | PASS | `toolbox/messages/index.html` adds the Voice Profiles table; `toolbox/messages/messages.js` creates/updates profiles through the Local API. | +| Table columns: Voice, Provider, Voice Name, Language, Updated, Actions | PASS | Voice Profiles table headers match the required column list. | +| Starter examples: Narrator, Hero, Merchant, Robot, Monster | PASS | `SEED_TTS_PROFILES` now includes all five required starter voice profiles. | +| Messages reference Voice Profiles | PASS | Message rows now require and return `voiceProfileKey` / `voiceProfileName`. | +| Message Parts reference Voice Profiles | PASS | Message Part rows now require and return `voiceProfileKey` / `voiceProfileName`. | +| No browser-owned authoritative product data | PASS | Voice profiles and references are created through the Local API; keys remain server/API owned. | +| No browser-generated authoritative database keys | PASS | The service continues to generate ULID-style keys server-side. | +| No TTS implementation in this PR | PASS | No playback buttons, Browser Speech runtime, or provider runtime behavior was added. | +| Theme V2 and reusable table patterns | PASS | Uses `card`, `surface-header`, `table-wrapper`, `data-table`, `action-group`, and `btn`. | +| No inline styles, style blocks, inline handlers, or inline script blocks | PASS | Static scan found no disallowed inline HTML patterns in `toolbox/messages/index.html`. | +| Creator-safe empty/error states | PASS | UI failure messages remain generic and do not expose server details. | +| Use Local API / Local DB wording | PASS | The Messages page persistence label now uses Local DB wording. | +| Delete blocked when referenced | PASS | No new direct delete path was introduced for voice profiles; existing message delete guard remains intact. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check toolbox/messages/messages.js; node --check src/dev-runtime/messages/messages-postgres-service.mjs; node --check tests/playwright/tools/MessagesTool.spec.mjs; node --check tests/dev-runtime/DbSeedIntegrity.test.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Local API contract | PASS | Inline Node probe verified starter voice profiles, required voice references, response voice names, and default backfill for legacy rows. | +| Messages Local API seed subtest | PASS | `node --test --test-name-pattern "Messages Local API seeds" tests/dev-runtime/DbSeedIntegrity.test.mjs` | +| Diff hygiene | PASS | `git diff --check -- src/dev-runtime/messages/messages-postgres-service.mjs tests/dev-runtime/DbSeedIntegrity.test.mjs tests/playwright/tools/MessagesTool.spec.mjs toolbox/messages/index.html toolbox/messages/messages.js` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --grep "Message Studio uses the approved table-first Messages structure" --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- The full `DbSeedIntegrity.test.mjs` file still has unrelated Local DB snapshot failures, but its Messages Local API subtest passes after the voice-profile changes. +- Generated fallback lane side reports were restored; only PR_004-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_004-voice-profiles-table.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_004-voice-profiles-table_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_005-message-reference-usage.md b/docs_build/dev/reports/PR_26174_BRAVO_005-message-reference-usage.md new file mode 100644 index 000000000..fce22de86 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_005-message-reference-usage.md @@ -0,0 +1,51 @@ +# PR_26174_BRAVO_005-message-reference-usage + +## Scope +- Added selected-message usage count display. +- Added a Reference Usage viewer that lists Message Part references for the selected Message. +- Added Archive/Restore actions for messages so referenced records can be archived instead of deleted. +- Kept delete blocked while a message has Message Part references. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_005 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Add usage count display | PASS | Inspector now shows `data-messages-usage-count` for the selected Message. | +| Add reference viewer | PASS | Inspector now renders `data-messages-reference-list` with Message Part references and previews. | +| Block delete when referenced | PASS | Referenced message delete remains disabled in the UI and rejected by the Local API. | +| Prefer archive when referenced | PASS | Referenced messages expose an Archive action while Delete remains blocked. | +| No direct deletion if in use | PASS | Service contract verified delete rejection before and after archiving while a Message Part reference exists. | +| Theme V2 only | PASS | Added the viewer through existing `vertical-accordion` / `accordion-body` / `content-stack` patterns. | +| No inline styles, style blocks, inline handlers, or inline script blocks | PASS | Static scan found no disallowed inline HTML patterns in `toolbox/messages/index.html`. | +| No browser-owned authoritative product data | PASS | Archive/restore uses the Local API update path; no browser-owned product data was introduced. | +| Creator-safe errors | PASS | Archive failure text is generic and does not expose server details. | +| No TTS implementation | PASS | No playback/runtime/provider behavior was added. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check toolbox/messages/messages.js; node --check tests/playwright/tools/MessagesTool.spec.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Reference usage contract | PASS | Inline Node probe verified referenced delete rejection, archive success, continued delete block while referenced, and delete success after reference removal. | +| Diff hygiene | PASS | `git diff --check -- toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --grep "Message Studio disables Delete when a message is referenced" --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- Static DOM checks and a Local API service contract probe covered the impacted reference/delete/archive behavior. +- Generated fallback lane side reports were restored; only PR_005-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_005-message-reference-usage.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_005-message-reference-usage_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_006-browser-tts-runtime.md b/docs_build/dev/reports/PR_26174_BRAVO_006-browser-tts-runtime.md new file mode 100644 index 000000000..66465dba7 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_006-browser-tts-runtime.md @@ -0,0 +1,49 @@ +# PR_26174_BRAVO_006-browser-tts-runtime + +## Scope +- Added Browser Speech API preview controls for the selected Message or Message Part. +- Implemented the runtime flow: Message -> Message Part -> Emotion Profile -> Voice Profile -> Browser Speech API. +- Added Stop Speech handling. +- Kept vendor provider integration out of scope. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_006 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Implement Browser Speech API runtime | PASS | `toolbox/messages/messages.js` uses `window.speechSynthesis` and `SpeechSynthesisUtterance`. | +| Flow Message -> Message Part -> Emotion Profile -> Voice Profile -> Browser Speech API | PASS | Speech queue resolves selected part, or selected message parts in order, then applies emotion/voice profile settings to utterances. | +| Add test playback for selected message/part | PASS | Focused Playwright spec stubs `speechSynthesis` and asserts selected message and selected part utterance payloads. | +| No vendor provider integration yet | PASS | Runtime accepts only `browser-speech` provider and shows Creator-safe failure for any other provider. | +| No silent provider fallback | PASS | Missing Browser Speech API, missing profile, unavailable provider, or unavailable named voice fails with Creator-safe UI text. | +| Theme V2 only | PASS | Speech controls use existing `vertical-accordion`, `accordion-body`, `content-stack`, `action-group`, and `btn` classes. | +| No inline styles, style blocks, inline handlers, or inline script blocks | PASS | Static scan found no disallowed inline HTML patterns in `toolbox/messages/index.html`. | +| No browser-owned authoritative product data | PASS | Speech preview reads existing Local API-backed message/profile data and persists nothing. | +| Creator-safe runtime failure text | PASS | Speech failures use generic user-facing messages and do not expose browser exception details. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check toolbox/messages/messages.js; node --check tests/playwright/tools/MessagesTool.spec.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Diff hygiene | PASS | `git diff --check -- toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --grep "Message Studio uses the approved table-first Messages structure" --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- Runtime behavior is covered in the focused Playwright spec with a deterministic `speechSynthesis` stub, ready to execute once Chromium is installed. +- Generated fallback lane side reports were restored; only PR_006-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_006-browser-tts-runtime.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_006-browser-tts-runtime_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_007-tts-provider-framework.md b/docs_build/dev/reports/PR_26174_BRAVO_007-tts-provider-framework.md new file mode 100644 index 000000000..fb9edbcde --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_007-tts-provider-framework.md @@ -0,0 +1,49 @@ +# PR_26174_BRAVO_007-tts-provider-framework + +## Scope +- Added an approved TTS provider framework. +- Providers: Browser Speech API, ElevenLabs, OpenAI, Azure, Polly. +- Kept Browser Speech API as the only active runtime provider. +- Added explicit provider validation and Creator-safe runtime failure behavior for unavailable providers. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_007 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Add provider abstraction | PASS | `toolbox/messages/messages.js` defines `TTS_PROVIDER_REGISTRY`; service validates approved provider keys. | +| Providers include Browser Speech API, ElevenLabs, OpenAI, Azure, Polly | PASS | Provider registry and Voice Profile select expose all five providers. | +| Browser Speech API remains only active runtime provider | PASS | Runtime gate allows only `browser-speech`; vendor providers remain inactive placeholders. | +| No silent provider fallback | PASS | Unknown, inactive, or config-required providers fail instead of falling back. | +| Invalid/missing provider config Creator-safe errors | PASS | UI catches provider runtime failures with generic speech-preview messages; service rejects unsupported provider keys. | +| No vendor provider integration | PASS | No vendor network calls, credentials, SDKs, or provider runtime implementations were added. | +| No browser-owned authoritative product data | PASS | Provider choices are saved through Local API voice profiles only. | +| Theme V2 / no inline HTML violations | PASS | This PR does not add page-local CSS or inline HTML handlers/styles/scripts. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check toolbox/messages/messages.js; node --check src/dev-runtime/messages/messages-postgres-service.mjs; node --check tests/playwright/tools/MessagesTool.spec.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/messages/index.html` returned no matches. | +| Provider contract | PASS | Inline Node probe verified approved provider persistence and unsupported provider rejection. | +| Diff hygiene | PASS | `git diff --check -- src/dev-runtime/messages/messages-postgres-service.mjs toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --grep "Message Studio uses the approved table-first Messages structure" --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- Focused spec now asserts the approved provider options and Browser Speech API provider label. +- Generated fallback lane side reports were restored; only PR_007-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_007-tts-provider-framework.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_007-tts-provider-framework_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_008-message-event-actions.md b/docs_build/dev/reports/PR_26174_BRAVO_008-message-event-actions.md new file mode 100644 index 000000000..eb68e18e3 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_008-message-event-actions.md @@ -0,0 +1,50 @@ +# PR_26174_BRAVO_008-message-event-actions + +## Scope +- Added server-owned Message Event Action records through the Local API. +- Added Events page support for Show Message, Speak Message, and Wait For Continue. +- Added message references from event actions to Messages. +- Avoided browser-owned event data; the Events page only renders/saves Local API records. + +## Branch Validation +| Check | Result | Notes | +| --- | --- | --- | +| Bravo branch retained | PASS | Current branch is `team/BRAVO/messages`. | +| Did not return to main | PASS | PR_008 was built as a stacked delta on the Bravo branch. | +| Did not merge or push main | PASS | No merge or push commands were run. | + +## Requirement Checklist +| Requirement | Result | Evidence | +| --- | --- | --- | +| Add Show Message option | PASS | Event action option `show-message` is supported by service and Events UI. | +| Add Speak Message option | PASS | Event action option `speak-message` is supported by service and Events UI. | +| Add Wait For Continue option | PASS | Event action option `wait-for-continue` is supported by service and Events UI. | +| Support references from Events to Messages | PASS | `messages_event_actions.messageKey` references Messages for Show/Speak actions. | +| Do not create browser-owned event data | PASS | Events page loads and saves action rows through `/api/messages/event-actions`; no local browser event records are persisted. | +| Creator-safe errors | PASS | Events UI uses generic Local API failure messages. | +| No inline styles, style blocks, inline handlers, or inline script blocks | PASS | Static scan found no disallowed inline HTML patterns in `toolbox/events/index.html`. | +| Theme V2 only | PASS | Events UI uses existing Theme V2 cards, table, accordion, button, and action-group classes. | +| No TTS provider expansion | PASS | PR only stores event action references; it does not add vendor/runtime provider behavior. | + +## Validation Lane Report +| Lane | Result | Command / Evidence | +| --- | --- | --- | +| Syntax | PASS | `node --check src/dev-runtime/messages/messages-postgres-service.mjs; node --check toolbox/events/events.js; node --check tests/playwright/tools/EventsTool.spec.mjs` | +| Inline HTML guard | PASS | `rg -n --pcre2 ']*\\bsrc=)' toolbox/events/index.html` returned no matches. | +| Event action contract | PASS | Inline Node probe verified Show/Speak message requirements, Wait For Continue no-message rule, unsupported action rejection, and message reference output. | +| Diff hygiene | PASS | `git diff --check -- src/dev-runtime/messages/messages-postgres-service.mjs toolbox/events/index.html toolbox/events/events.js tests/playwright/tools/EventsTool.spec.mjs` reported line-ending warnings only. | +| Targeted Playwright | BLOCKED | `npx playwright test tests/playwright/tools/EventsTool.spec.mjs --project=playwright` failed because Chromium executable `chromium-1217` is not installed. | +| Fallback validation | BLOCKED | `npm run test:workspace-v2` failed for the same missing Playwright Chromium executable. | + +## Manual Validation Notes +- Browser validation could not complete because this workstation is missing the Playwright Chromium executable. +- Focused Events spec was added to cover the Local API-backed UI path once Chromium is available. +- Generated fallback lane side reports were restored; only PR_008-required report files remain in the PR delta. + +## Reports And Package +| Artifact | Path | +| --- | --- | +| Review diff | `docs_build/dev/reports/codex_review.diff` | +| Changed files | `docs_build/dev/reports/codex_changed_files.txt` | +| PR report | `docs_build/dev/reports/PR_26174_BRAVO_008-message-event-actions.md` | +| Delta ZIP | `tmp/PR_26174_BRAVO_008-message-event-actions_delta.zip` | diff --git a/docs_build/dev/reports/PR_26174_BRAVO_009-message-publish-validation.md b/docs_build/dev/reports/PR_26174_BRAVO_009-message-publish-validation.md new file mode 100644 index 000000000..dbab4647b --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_009-message-publish-validation.md @@ -0,0 +1,55 @@ +# PR_26174_BRAVO_009-message-publish-validation + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Work continued on the stacked Bravo branch. +- PASS: No merge was performed. +- PASS: No push to `main` was performed. +- PASS: `start_of_day` folders were not modified. + +## Scope Summary +- Added server-owned Messages Local API publish validation at `/api/messages/publish-validation`. +- Added a Message Studio Publish Validation inspector panel with external JavaScript behavior. +- Added focused dev-runtime coverage for publish-ready and publish-blocked message/TTS configurations. +- Updated the Messages Playwright spec to cover the visible validation panel and successful validation flow. + +## Requirement Checklist +- PASS: Missing message text is reported for Messages and Message Parts. +- PASS: Missing Emotion Profile is reported. +- PASS: Missing Voice Profile is reported. +- PASS: Broken Message, Emotion Profile, Voice Profile, and Event Action references are reported. +- PASS: Invalid provider assignment is reported; Browser Speech API is the only publish-ready provider. +- PASS: Publish validation returns `canPublish: false` and `valid: false` for invalid message/TTS configuration. +- PASS: Theme V2 classes and existing external JavaScript patterns are used. +- PASS: No inline styles, style blocks, inline handlers, or inline script blocks were added. +- PASS: No browser-owned authoritative product data was added. +- PASS: No browser-generated authoritative database keys were added. +- PASS: Local API / Local DB wording is preserved. +- PASS: API/runtime failure text is Creator-safe. + +## Validation Lane Report +- PASS: `node --check src\dev-runtime\messages\messages-postgres-service.mjs` +- PASS: `node --check toolbox\messages\messages-api-client.js` +- PASS: `node --check toolbox\messages\messages.js` +- PASS: `node --check tests\dev-runtime\MessagesPublishValidation.test.mjs` +- PASS: `node --check tests\playwright\tools\MessagesTool.spec.mjs` +- PASS: HTML inline/style/script guard on `toolbox/messages/index.html` +- PASS: Ownership guard for `imageDataUrl`, browser-owned wording, `localStorage`, and `sessionStorage` +- PASS: `node --test tests\dev-runtime\MessagesPublishValidation.test.mjs` +- PASS: `git diff --check` for PR_009 touched files, with line-ending warnings only +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` + - Blocker: Playwright Chromium executable is not installed in the local `ms-playwright` cache. +- BLOCKED: `npm run test:workspace-v2` + - Blocker: Same missing Playwright Chromium executable. + +## Manual Validation Notes +- Verified the Messages tool has a visible Publish Validation panel with `Run Validation`, status, and issue list. +- Verified validation result rendering supports Not checked, Ready, and Blocked states. +- Verified validation failures are Creator-safe and do not expose server or database internals. +- Browser visual/manual validation still needs a local Playwright browser install before the Playwright lane can launch. + +## Reports And Package +- Shared diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- PR report: `docs_build/dev/reports/PR_26174_BRAVO_009-message-publish-validation.md` +- Delta ZIP: `tmp/PR_26174_BRAVO_009-message-publish-validation_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_010-separate-messages-and-tts-ownership.md b/docs_build/dev/reports/PR_26174_BRAVO_010-separate-messages-and-tts-ownership.md new file mode 100644 index 000000000..c4b6d9bfa --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_010-separate-messages-and-tts-ownership.md @@ -0,0 +1,63 @@ +# PR_26174_BRAVO_010-separate-messages-and-tts-ownership + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Work continued on the stacked Bravo branch. +- PASS: No merge was performed. +- PASS: No return to `main` was performed. +- PASS: No push to `main` was performed. +- PASS: `start_of_day` folders were not modified. + +## Scope Summary +- Separated visible ownership between Message Studio and Text To Speech. +- Message Studio now owns Messages and ordered sentence rows only. +- Text To Speech now owns TTS Profile rows and child emotion audio settings. +- Message Studio playback resolves through TTS Profile and sentence emotion settings. + +## Requirement Checklist +- PASS: Messages no longer shows Reusable Assets / Emotion Profiles sections. +- PASS: Messages no longer shows Voice Profiles sections. +- PASS: Messages parent table columns are Message, TTS Profile, Updated, Actions. +- PASS: Messages parent row Actions include Play at the end. +- PASS: Messages child table is sentences with Order, Text, Emotion, Actions. +- PASS: Messages references TTS Profile by key/name. +- PASS: Sentences reference Emotion by key/name from the selected TTS Profile option contract. +- PASS: Messages does not expose pitch, rate, volume, voice, or provider authoring controls. +- PASS: Text To Speech parent table columns are Profile, Gender, Voice, Language, Age Filter, Emotion Count, Status, Actions. +- PASS: Text To Speech child emotion table columns are Emotion, Pitch, Rate, Volume, Actions. +- PASS: Pitch, Rate, and Volume editors are range sliders. +- PASS: Emotion editor remains a dropdown. +- PASS: Browser Speech runtime remains connected through TTS Profile plus Emotion. +- PASS: Theme V2/external JavaScript pattern preserved. +- PASS: No inline styles, style blocks, inline handlers, or inline script blocks were added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys were added. +- PASS: Creator-safe failure text is preserved. + +## Validation Lane Report +- PASS: `node --check src\dev-runtime\messages\messages-postgres-service.mjs` +- PASS: `node --check toolbox\messages\messages.js` +- PASS: `node --check toolbox\text-to-speech\text2speech.js` +- PASS: `node --check tests\playwright\tools\MessagesTool.spec.mjs` +- PASS: `node --check tests\playwright\tools\TextToSpeechFunctional.spec.mjs` +- PASS: `node --check tests\tools\Text2SpeechShell.test.mjs` +- PASS: `node --test tests\tools\Text2SpeechShell.test.mjs` +- PASS: `node --test tests\dev-runtime\MessagesPublishValidation.test.mjs` +- PASS: HTML inline/style/script guard on `toolbox/messages/index.html` and `toolbox/text-to-speech/index.html` +- PASS: Ownership guard for `imageDataUrl`, `localStorage`, and `sessionStorage` +- PASS: `git diff --check` for PR_010 touched files, with line-ending warnings only +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs --project=playwright` + - Blocker: Playwright Chromium executable is not installed in the local `ms-playwright` cache. +- BLOCKED: `npm run test:workspace-v2` + - Blocker: Same missing Playwright Chromium executable. + +## Manual Validation Notes +- Reviewed Message Studio HTML/JS to confirm old profile cards are no longer rendered. +- Reviewed Text To Speech HTML/JS to confirm profile parent columns and slider-based emotion child rows. +- Reviewed Messages playback path: Message -> TTS Profile -> Sentence -> Emotion setting -> Browser Speech API. +- Browser visual validation still needs a local Playwright browser install before the Playwright lane can launch. + +## Reports And Package +- Shared diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- PR report: `docs_build/dev/reports/PR_26174_BRAVO_010-separate-messages-and-tts-ownership.md` +- Delta ZIP: `tmp/PR_26174_BRAVO_010-separate-messages-and-tts-ownership_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_011-message-sentence-action-buttons.md b/docs_build/dev/reports/PR_26174_BRAVO_011-message-sentence-action-buttons.md new file mode 100644 index 000000000..5a8c2768f --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_011-message-sentence-action-buttons.md @@ -0,0 +1,49 @@ +# PR_26174_BRAVO_011-message-sentence-action-buttons + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the Bravo branch without returning to `main`. +- PASS: No merge was performed. +- PASS: No `start_of_day` folders were modified. + +## Requirement Checklist +- PASS: Parent table remains `Game Text Repository` Messages. +- PASS: Parent Message table keeps columns `Message`, `TTS Profile`, `Updated`, `Actions`. +- PASS: Parent controls include Add Message, Edit Message, Save Message, Cancel Message, Delete/Archive Message, and Play Message. +- PASS: Parent Message rows expand to child Sentence rows. +- PASS: Child Sentence table keeps columns `Order`, `Text`, `Emotion`, `Actions`. +- PASS: Child controls include Add Sentence, Edit Sentence, Save Sentence, Cancel Sentence, Delete/Archive Sentence, and Play Sentence. +- PASS: Creator-facing Messages UI now uses Sentence/Sentences wording for the child records. +- PASS: Existing internal `segment` and `parts` identifiers are retained only where changing them would expand risk. +- PASS: Message Play queues ordered Sentences from the selected Message. +- PASS: Sentence Play uses the same Browser Speech runtime path for only the selected Sentence. +- PASS: Edit actions switch rows to Save/Cancel controls. +- PASS: Cancel leaves the previous row state intact. +- PASS: Message Delete remains blocked while the Message has Sentences, with Archive available. +- PASS: Sentence Archive/Restore is available as the preferred non-destructive action. +- PASS: Messages page does not show `Reusable Assets Emotion Profiles`, `Emotion Profiles`, or `Voice Profiles` sections. +- PASS: Messages page does not add pitch, rate, volume, voice, or provider ownership controls. +- PASS: No inline styles, style blocks, inline handlers, or HTML script blocks were added. +- PASS: No browser-owned authoritative product data was added. +- PASS: Errors added or changed are Creator-safe. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `rg -n --pcre2 "]*\\bsrc=)" toolbox/messages/index.html` returned no matches. +- PASS: `rg -n "localStorage|sessionStorage|imageDataUrl|Reusable Assets|Emotion Profiles|Voice Profiles|Message Parts" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` found only negative Playwright assertions for forbidden labels. +- PASS: `git diff --check -- toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` completed with no whitespace errors. Git emitted existing CRLF normalization warnings. +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: `npm run test:workspace-v2` reached the same missing Chromium blocker after its non-browser audits completed. + +## Manual Validation Notes +- Reviewed `toolbox/messages/index.html` and confirmed the Inspector label now says `Sentence`, not `Part`. +- Reviewed `toolbox/messages/messages.js` and confirmed Sentence rows render Edit, Archive/Restore, Delete, and Play actions. +- Reviewed playback flow and confirmed Message Play maps ordered Sentences through `speechItemFromSegment`, while Sentence Play queues only the selected Sentence. +- Reviewed Messages ownership and confirmed no profile management tables or TTS setting sliders were added to Messages. +- Browser click-through validation is pending a local Playwright Chromium install. + +## Review Artifacts +- Review diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- Delta ZIP: `tmp/PR_26174_BRAVO_011-message-sentence-action-buttons_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_012-tts-preview-action-cleanup.md b/docs_build/dev/reports/PR_26174_BRAVO_012-tts-preview-action-cleanup.md new file mode 100644 index 000000000..fea9e968c --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_012-tts-preview-action-cleanup.md @@ -0,0 +1,45 @@ +# PR_26174_BRAVO_012-tts-preview-action-cleanup + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the Bravo branch without returning to `main`. +- PASS: No merge was performed. +- PASS: No `start_of_day` folders were modified. + +## Requirement Checklist +- PASS: Updated only the Text To Speech page, related external JS, targeted tests, and required reports. +- PASS: Removed creator-visible `TTS Profile / Emotion Settings` child-table text. +- PASS: Removed creator-visible `Emotion Settings` heading/text from the Text To Speech surface. +- PASS: Kept Text To Speech ownership of TTS profile parent rows and Emotion child rows. +- PASS: Kept Pitch, Rate, and Volume as child Emotion row sliders in edit mode. +- PASS: Child Emotion table columns remain `Emotion`, `Pitch`, `Rate`, `Volume`, `Actions`. +- PASS: Added a Play button under Actions for each child Emotion row. +- PASS: Emotion-row Play speaks the Local Preview `Text To Speak` text. +- PASS: Emotion-row Play applies that row's Pitch, Rate, and Volume values. +- PASS: Local Preview area remains `Speech Composition` and `Text To Speak`. +- PASS: No inline styles, inline scripts, inline handlers, or page-local CSS were added. +- PASS: The implementation uses external JavaScript and Theme V2 classes already present on the page. +- PASS: Browser Speech unavailable path shows a Creator-safe message and does not silently fallback. + +## Validation Lane Report +- PASS: `node --check toolbox/text-to-speech/text2speech.js` +- PASS: `node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +- PASS: `rg -n --pcre2 "]*\\bsrc=)" toolbox/text-to-speech/index.html` returned no matches. +- PASS: `rg -n "TTS Profile / Emotion Settings|Emotion Settings|Emotion setting|Emotion Setting|Message Parts" toolbox/text-to-speech/index.html toolbox/text-to-speech/text2speech.js tests/playwright/tools/TextToSpeechFunctional.spec.mjs` found only negative Playwright assertions. +- PASS: `git diff --check -- toolbox/text-to-speech/index.html toolbox/text-to-speech/text2speech.js tests/playwright/tools/TextToSpeechFunctional.spec.mjs` completed with no whitespace errors. Git emitted existing CRLF normalization warnings. +- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` +- BLOCKED: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: `npm run test:workspace-v2` reached the same missing Chromium blocker after its non-browser audits completed. + +## Manual Validation Notes +- Reviewed `toolbox/text-to-speech/index.html` and confirmed Local Preview still shows `Speech Composition` and `Text To Speak`. +- Reviewed `toolbox/text-to-speech/text2speech.js` and confirmed the old child table heading/kicker nodes were removed. +- Reviewed the child Emotion row rendering and confirmed each row now includes Edit Emotion, Delete, and Play actions. +- Reviewed playback flow and confirmed row Play uses the selected profile, selected emotion row, Local Preview text, and row Pitch/Rate/Volume values. +- Reviewed unavailable browser speech flow and confirmed row Play writes the Creator-safe Web Speech API support message without fallback behavior. +- Browser click-through validation is pending a local Playwright Chromium install. + +## Review Artifacts +- Review diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- Delta ZIP: `tmp/PR_26174_BRAVO_012-tts-preview-action-cleanup_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_013-message-and-sentence-play-buttons.md b/docs_build/dev/reports/PR_26174_BRAVO_013-message-and-sentence-play-buttons.md new file mode 100644 index 000000000..14235b264 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_013-message-and-sentence-play-buttons.md @@ -0,0 +1,51 @@ +# PR_26174_BRAVO_013-message-and-sentence-play-buttons + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the Bravo branch without returning to `main`. +- PASS: No merge was performed. +- PASS: No `start_of_day` folders were modified. + +## Requirement Checklist +- PASS: Parent table record remains Message. +- PASS: Child table record remains Sentence. +- PASS: Parent Message rows have a Play button in Actions. +- PASS: Parent Play queues all child Sentences in display order. +- PASS: Child Sentence rows have a Play button in Actions. +- PASS: Child Play queues only the selected Sentence. +- PASS: Message playback resolves through Message, selected TTS Profile, ordered Sentences, Sentence Emotion, and TTS Profile emotion settings. +- PASS: Sentence playback resolves through Sentence, parent Message TTS Profile, Sentence Emotion, and TTS Profile emotion settings. +- PASS: Missing Sentences, TTS Profile, browser voice, or TTS Profile emotion settings produce Creator-safe guidance. +- PASS: Removed silent fallback from Message text when a Message has no Sentences. +- PASS: Removed fallback from Message-owned/global emotion profiles during playback; playback now requires the selected TTS Profile emotion setting. +- PASS: Messages does not gain voice, emotion slider, pitch, rate, or volume ownership. +- PASS: Messages owns what to speak and Text To Speech owns how to speak. +- PASS: Text To Speech Profile add/edit refreshes and displays available browser voices in the Voice dropdown. +- PASS: Voice selection is preserved through profile edit and used by Emotion-row Play. +- PASS: Emotion-row Play uses selected profile voice plus row Pitch, Rate, and Volume. +- PASS: Local Preview `Text To Speak` remains the preview text source. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check toolbox/text-to-speech/text2speech.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +- PASS: `rg -n --pcre2 "]*\\bsrc=)" toolbox/messages/index.html toolbox/text-to-speech/index.html` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js toolbox/text-to-speech/text2speech.js tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs` completed with no whitespace errors. Git emitted existing CRLF normalization warnings. +- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl|style=|]*src=)" toolbox/messages toolbox/text-to-speech tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs` returned no matches. +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: `npm run test:workspace-v2` reached the same missing Chromium blocker after its non-browser audits completed. + +## Manual Validation Notes +- Reviewed Messages playback code and confirmed parent Play blocks when no child Sentences exist. +- Reviewed Messages playback code and confirmed Sentence Play uses the parent Message TTS Profile, not Sentence-owned voice settings. +- Reviewed Messages playback code and confirmed browser voice lookup requires an available selected voice instead of silently using browser defaults. +- Reviewed Text To Speech profile editor code and confirmed add/edit refreshes browser voices before rendering the Voice dropdown. +- Reviewed Text To Speech Emotion-row Play and confirmed it sends selected voice, Local Preview text, and row Pitch/Rate/Volume to the speech engine. +- Browser click-through validation is pending a local Playwright Chromium install. + +## Review Artifacts +- Review diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- Delta ZIP: `tmp/PR_26174_BRAVO_013-message-and-sentence-play-buttons_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_014-message-play-button-regression-fix.md b/docs_build/dev/reports/PR_26174_BRAVO_014-message-play-button-regression-fix.md new file mode 100644 index 000000000..ec7f9610d --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_014-message-play-button-regression-fix.md @@ -0,0 +1,45 @@ +# PR_26174_BRAVO_014-message-play-button-regression-fix + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the Bravo branch without returning to `main`. +- PASS: No merge was performed. +- PASS: No `start_of_day` folders were modified. + +## Requirement Checklist +- PASS: Parent table record remains Message. +- PASS: Child table record remains Sentence. +- PASS: Parent Message row Actions always render Play, Edit, Archive/Restore, and Delete. +- PASS: Parent Play remains visible after parent Archive/Restore state changes. +- PASS: Parent Play remains visible after child Sentence Delete state changes. +- PASS: Parent Play speaks ordered child Sentences. +- PASS: Child Sentence row Actions render Play, Edit, Archive/Restore, and Delete. +- PASS: Child Play speaks only that Sentence. +- PASS: Delete/Archive actions do not remove unrelated Play actions. +- PASS: Creator-facing UI continues to use Sentence/Sentences, not Message Parts. +- PASS: Messages owns what to speak; no TTS sliders or voice/provider ownership were moved into Messages. +- PASS: Playback continues to resolve through selected TTS Profile and selected Sentence Emotion. +- PASS: Existing Creator-safe guidance remains for missing profile, voice, emotion, sentence text, or Sentences. +- PASS: No silent fallback was added. +- PASS: No inline styles, inline scripts, inline handlers, or page-local CSS were added. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `rg --pcre2 -n "]*\\bsrc=)|Message Parts" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` found only the negative Playwright assertion for `Message Parts`. +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` completed with no whitespace errors. Git emitted existing CRLF normalization warnings. +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: `npm run test:workspace-v2` reached the same missing Chromium blocker after its non-browser audits completed. + +## Manual Validation Notes +- Reviewed parent Message row rendering and confirmed Play is rendered first in the Actions group, followed by Sentences, Edit, Archive/Restore, and Delete. +- Reviewed child Sentence row rendering and confirmed Play is rendered first in the Actions group, followed by Edit, Archive/Restore, and Delete. +- Reviewed Messages playback code and confirmed parent Play queues ordered Sentences while child Play queues one selected Sentence. +- Reviewed Playwright coverage additions for child Play one-row speech, parent Play ordered speech, parent Archive/Restore visibility, and child Delete/Archive visibility preservation. +- Browser click-through validation is pending a local Playwright Chromium install. + +## Review Artifacts +- Review diff: `docs_build/dev/reports/codex_review.diff` +- Changed files: `docs_build/dev/reports/codex_changed_files.txt` +- Delta ZIP: `tmp/PR_26174_BRAVO_014-message-play-button-regression-fix_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_015-child-play-uses-parent-tts-profile.md b/docs_build/dev/reports/PR_26174_BRAVO_015-child-play-uses-parent-tts-profile.md new file mode 100644 index 000000000..e3b8dc4bc --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_015-child-play-uses-parent-tts-profile.md @@ -0,0 +1,46 @@ +# PR_26174_BRAVO_015-child-play-uses-parent-tts-profile + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch; no merge and no return to main. + +## Purpose +- PASS: Scoped to Messages playback resolving child Sentence Play through the parent Message TTS Profile. + +## Requirement Checklist +- PASS: Parent Message owns the selected TTS Profile; playback items resolve `message.voiceProfileKey` before speaking. +- PASS: Child Sentence owns only Text and Emotion in the Messages UI; voice/profile settings remain outside sentence rows. +- PASS: Child Sentence Play resolves Sentence -> Parent Message -> TTS Profile -> Profile Voice -> Sentence Emotion -> Profile Emotion Settings. +- PASS: Parent Message Play resolves Message -> TTS Profile -> ordered Sentences -> Profile Voice and each Sentence Emotion. +- PASS: Messages playback does not use Local Preview voice selection or the Text To Speech preview workflow. +- PASS: The old Messages playback failure `Select an available browser voice before preview.` is not present in `toolbox/messages` and is covered by negative Playwright assertions. +- PASS: Missing TTS Profile, missing profile voice, missing sentence emotion, and profile/emotion mismatch use Creator-safe guidance. +- PASS: Text To Speech remains the owner of profile voice and emotion slider settings. +- PASS: No inline styles, inline scripts, inline handlers, style blocks, or page-local CSS were added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys were added. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `rg --pcre2 -n "]*\\bsrc=)" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg -n "Select an available browser voice before preview|Local Preview|Text To Speak" toolbox/messages` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: Fallback `npm run test:workspace-v2` reached the same missing Chromium blocker after completing the static/preflight report generation. + +## Manual Validation Notes +- Reviewed `speechItemFromSegment`: child Sentence Play now derives the profile key from the parent Message and does not use any sentence/local preview voice value. +- Reviewed `speakQueue`: profile voice and profile emotion settings are applied before `speechSynthesis.speak`. +- Updated Playwright coverage to select the Hero TTS Profile, assert child Sentence Play speaks only one sentence with `Browser hero`, assert parent Message Play speaks all ordered sentences with `Browser hero`, and assert the old preview failure does not appear from Messages playback. +- Browser-backed manual execution was not possible in this environment because the required Playwright Chromium binary is not installed. + +## Changed Files +- docs_build/dev/reports/PR_26174_BRAVO_015-child-play-uses-parent-tts-profile.md +- docs_build/dev/reports/codex_review.diff +- docs_build/dev/reports/codex_changed_files.txt +- tests/playwright/tools/MessagesTool.spec.mjs +- toolbox/messages/messages.js + +## Artifact +- tmp/PR_26174_BRAVO_015-child-play-uses-parent-tts-profile_delta.zip diff --git a/docs_build/dev/reports/PR_26174_BRAVO_017-message-play-profile-resolution.md b/docs_build/dev/reports/PR_26174_BRAVO_017-message-play-profile-resolution.md new file mode 100644 index 000000000..8d67c8d93 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_017-message-play-profile-resolution.md @@ -0,0 +1,50 @@ +# PR_26174_BRAVO_017-message-play-profile-resolution + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch; no merge and no return to main. + +## Purpose +- PASS: Scoped to Messages playback profile resolution and diagnostics. + +## Requirement Checklist +- PASS: Parent Message Play still exists in the parent row Actions area. +- PASS: Child Sentence Play still exists in child row Actions. +- PASS: Parent Play resolves the selected parent TTS Profile before speaking ordered Sentences. +- PASS: Child Play resolves Sentence -> Parent Message -> selected TTS Profile before speaking only that Sentence. +- PASS: Message playback does not read Local Preview voice selection, preview voice dropdown, or preview voice state. +- PASS: Message playback resolves Voice from the selected TTS Profile voice field. +- PASS: Playback diagnostics display Profile, Gender, Voice, Language, and Age Filter. +- PASS: Missing gender or age-filter metadata is displayed with Creator-safe `Any` defaults rather than exposing implementation details. +- PASS: The old `Select an available browser voice before preview.` message is absent from the Messages runtime and covered by negative Playwright assertions. +- PASS: Text To Speech remains the owner of profile voice/filter/audio settings; Messages only reads the selected profile for playback. +- PASS: No inline styles, inline scripts, inline handlers, style blocks, or page-local CSS were added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys were added. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `rg --pcre2 -n "]*\\bsrc=)" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg -n "Select an available browser voice before preview|Local Preview|Preview voice|data-tts-preview|data-tts-voice" toolbox/messages/index.html toolbox/messages/messages.js` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: `npx playwright install chromium` was attempted once and timed out after three minutes without producing the required Chromium executable. The timed-out installer helper processes were stopped; the existing local API process was left running. +- BLOCKED: Fallback `npm run test:workspace-v2` reached the same missing Chromium blocker after completing static/preflight report generation. + +## Manual Validation Notes +- Reviewed `speechItemFromSegment`: child playback continues to derive `profileKey` from the parent message, not from sentence-owned voice data or preview state. +- Reviewed `browserVoiceForProfile`: Messages playback uses the selected profile voice name and never calls the Text To Speech Local Preview voice selector. +- Reviewed new diagnostics: the status log now persists `Playing:`, `Profile:`, `Gender:`, `Voice:`, `Language:`, and `Age Filter:` for playback. +- Updated Playwright assertions to verify the Hero profile diagnostics for both child Sentence Play and parent Message Play, and to keep guarding against the old preview voice error. +- Browser-backed manual execution was not possible in this environment because the required Playwright Chromium binary is not installed. + +## Changed Files +- docs_build/dev/reports/PR_26174_BRAVO_017-message-play-profile-resolution.md +- docs_build/dev/reports/codex_review.diff +- docs_build/dev/reports/codex_changed_files.txt +- tests/playwright/tools/MessagesTool.spec.mjs +- toolbox/messages/messages.js + +## Artifact +- tmp/PR_26174_BRAVO_017-message-play-profile-resolution_delta.zip diff --git a/docs_build/dev/reports/PR_26174_BRAVO_018-fix-messages-playback-source.md b/docs_build/dev/reports/PR_26174_BRAVO_018-fix-messages-playback-source.md new file mode 100644 index 000000000..88cf83146 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_018-fix-messages-playback-source.md @@ -0,0 +1,48 @@ +# PR_26174_BRAVO_018-fix-messages-playback-source + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch; no merge and no return to main. + +## Purpose +- PASS: Scoped to fixing the Messages playback source so it no longer uses Text To Speech preview-style exact browser voice validation. + +## Requirement Checklist +- PASS: Parent Message row Actions include Play, Edit, Archive/Restore, and Delete. +- PASS: Child Sentence row Actions include Play, Edit, Archive/Restore, and Delete. +- PASS: Parent Play resolves Message -> selected TTS Profile -> ordered child Sentences -> speech queue. +- PASS: Child Play resolves Sentence -> parent Message -> selected TTS Profile -> single-sentence speech queue. +- PASS: Messages playback no longer requires an exact browser voice match for the selected profile voice text. +- PASS: When a selected profile has Voice text but the browser cannot match it exactly, Messages allows Browser Speech to use its default voice. +- PASS: Diagnostics still display the selected profile values, including Profile, Gender, Voice, Language, and Age Filter. +- PASS: Messages runtime does not show `Select an available browser voice before preview.` and does not depend on Local Preview voice selection, preview voice dropdown, or preview voice state. +- PASS: No changes were made under `toolbox/text-to-speech/`. +- PASS: No inline styles, inline scripts, inline handlers, style blocks, or page-local CSS were added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys were added. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `rg --pcre2 -n "]*\\bsrc=)" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` returned no matches. +- PASS: `rg -n "Select an available browser voice before preview|Local Preview|Preview voice|data-tts-preview|data-tts-voice" toolbox/messages/index.html toolbox/messages/messages.js` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs` +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: Fallback `npm run test:workspace-v2` reached the same missing Chromium blocker after completing static/preflight report generation. + +## Manual Validation Notes +- Reviewed `browserVoiceForProfile`: it now returns an exact voice match when available and returns `null` when unavailable so Web Speech can use its browser default voice. +- Reviewed `assertSpeechItem`: Messages still requires selected parent TTS Profile voice text; it no longer requires that text to be installed as an exact local browser voice. +- Updated the Messages Playwright fixture to omit `Browser hero` from `speechSynthesis.getVoices()` while the selected Hero profile still has `voiceName: Browser hero`. +- Updated assertions so Child Play and Parent Play still speak with no matched browser voice while diagnostics continue to display `Voice: Browser hero`. +- Browser-backed manual execution was not possible in this environment because the required Playwright Chromium binary is not installed. + +## Changed Files +- docs_build/dev/reports/PR_26174_BRAVO_018-fix-messages-playback-source.md +- docs_build/dev/reports/codex_review.diff +- docs_build/dev/reports/codex_changed_files.txt +- tests/playwright/tools/MessagesTool.spec.mjs +- toolbox/messages/messages.js + +## Artifact +- tmp/PR_26174_BRAVO_018-fix-messages-playback-source_delta.zip diff --git a/docs_build/dev/reports/PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play.md b/docs_build/dev/reports/PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play.md new file mode 100644 index 000000000..a399aac62 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play.md @@ -0,0 +1,53 @@ +# PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch; no merge and no return to main. + +## Purpose +- PASS: Scoped to removing preview-style dependency and wording from Messages playback. + +## Requirement Checklist +- PASS: Parent Message row Actions include Play, Edit, Archive/Restore, and Delete. +- PASS: Child Sentence row Actions include Play, Edit, Archive/Restore, and Delete. +- PASS: Parent Play resolves Message -> selected TTS Profile -> ordered child Sentences -> speech queue. +- PASS: Child Play resolves Sentence -> parent Message -> selected TTS Profile -> single-sentence speech queue. +- PASS: Messages playback does not call Text To Speech Local Preview logic and does not read preview voice state. +- PASS: `toolbox/messages/messages.js` contains no `before preview`, no `available browser voice before preview`, and no exact preview voice error text. +- PASS: Messages runtime speech status wording now uses playback instead of preview. +- PASS: Playback diagnostics display Profile, Gender, Voice, Language, and Age Filter from the parent profile. +- PASS: If exact browser voice matching fails, Messages allows browser default speech while keeping selected profile values visible. +- PASS: Text To Speech preview strict validation was not changed. +- PASS: No inline styles, inline scripts, inline handlers, style blocks, or page-local CSS were added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys were added. + +## Validation Lane Report +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `node --check tests/tools/MessagesPlaybackSource.test.mjs` +- PASS: `node --test tests/tools/MessagesPlaybackSource.test.mjs` +- PASS: `rg --pcre2 -n "]*\\bsrc=)" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs tests/tools/MessagesPlaybackSource.test.mjs` returned no matches. +- PASS: `rg --pcre2 -n "localStorage|sessionStorage|imageDataUrl" toolbox/messages/index.html toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs tests/tools/MessagesPlaybackSource.test.mjs` returned no matches. +- PASS: `rg -n "before preview|available browser voice before preview|Select an available browser voice before preview" toolbox/messages/messages.js` returned no matches. +- PASS: `rg -n "Speech preview|speech preview|previewing|browser preview|speechPreview" toolbox/messages/messages.js` returned no matches. +- PASS: `git diff --check -- toolbox/messages/messages.js tests/playwright/tools/MessagesTool.spec.mjs tests/tools/MessagesPlaybackSource.test.mjs` +- BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` could not launch because Chromium is missing at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- BLOCKED: Fallback `npm run test:workspace-v2` reached the same missing Chromium blocker after completing static/preflight report generation. + +## Manual Validation Notes +- Reviewed `browserVoiceForProfile`: it returns a matched browser voice when available, otherwise `null` so Browser Speech can use its default voice. +- Reviewed `speechItemFromSegment`: child Sentence Play still inherits the selected parent Message TTS Profile. +- Reviewed runtime copy: Messages playback no longer presents preview-status wording from `messages.js`. +- Added a Node static test to block the preview voice error text from returning to `toolbox/messages/messages.js`. +- Browser-backed manual execution was not possible in this environment because the required Playwright Chromium binary is not installed. + +## Changed Files +- docs_build/dev/reports/PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play.md +- docs_build/dev/reports/codex_review.diff +- docs_build/dev/reports/codex_changed_files.txt +- tests/playwright/tools/MessagesTool.spec.mjs +- tests/tools/MessagesPlaybackSource.test.mjs +- toolbox/messages/messages.js + +## Artifact +- tmp/PR_26174_BRAVO_019-remove-preview-dependency-from-messages-play_delta.zip diff --git a/docs_build/dev/reports/PR_26174_BRAVO_020-messages-load-tts-profile-emotions.md b/docs_build/dev/reports/PR_26174_BRAVO_020-messages-load-tts-profile-emotions.md new file mode 100644 index 000000000..88b86e766 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_020-messages-load-tts-profile-emotions.md @@ -0,0 +1,68 @@ +# PR_26174_BRAVO_020-messages-load-tts-profile-emotions + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch. +- PASS: Did not merge and did not return to `main`. + +## Summary +- Added a Text To Speech-owned Message Studio profile export with profile-specific emotions. +- Updated the Messages Local API TTS profile seed/response mapping to use the Text To Speech profile contract while preserving server-owned database keys. +- Updated Messages sentence Emotion dropdowns to show only emotions available on the selected parent Message TTS Profile. +- Added/updated tests for TTS-owned profile names, filtered child emotion options, parent profile switching, and Messages playback through selected profile/emotion references. + +## Requirement Checklist +- PASS: `/toolbox/messages/index.html` continues to load Messages through external JS; TTS profile names are now supplied by the Text To Speech-owned profile contract through the Local API. +- PASS: Messages parent TTS Profile dropdown shows available TTS Profiles: `Default Balanced Profile`, `Man Profile 1`, and `Woman Profile 2`. +- PASS: Child Sentence Emotion dropdown is filtered by the selected parent TTS Profile. +- PASS: `Man Profile 1` exposes only `Calm` and `Urgent`. +- PASS: `Woman Profile 2` exposes only `Whisper` and `Robot`. +- PASS: Switching parent profile updates available child Sentence emotions. +- PASS: Messages does not duplicate TTS profile/emotion ownership; Text To Speech owns profile, voice, language, gender, age filter, pitch, rate, and volume. +- PASS: Messages still owns what to speak. +- PASS: Messages playback resolves Message/Sentence to selected parent TTS Profile and selected Sentence Emotion. +- PASS: Messages runtime still contains no `Select an available browser voice before preview` error text. +- PASS: No inline styles, inline scripts, inline handlers, or page-local CSS added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys added. +- PASS: Creator-safe failure text retained. + +## Validation Lane Report +- PASS: `node tests/tools/Text2SpeechShell.test.mjs` +- PASS: `node tests/tools/MessagesPlaybackSource.test.mjs` +- PASS: `node --test-name-pattern "Messages Local API seeds" tests/dev-runtime/DbSeedIntegrity.test.mjs` +- PASS: `node tests/dev-runtime/MessagesPublishValidation.test.mjs` +- PASS: Service inspection confirmed profile/emotion response shape: + - `Default Balanced Profile`: `Calm`, `Urgent` + - `Man Profile 1`: `Calm`, `Urgent` + - `Woman Profile 2`: `Whisper`, `Robot` +- FAIL/BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=chromium` + - Repo Playwright config has projects `ui` and `playwright`; no `chromium` project. +- FAIL/BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` + - Blocked before assertions: missing local Chromium executable at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- FAIL/BLOCKED: `npm run test:workspace-v2` + - Static setup ran, then blocked by the same missing Chromium executable. +- PASS: `npm run test:playwright:static` +- NOTE: Full `node tests/dev-runtime/DbSeedIntegrity.test.mjs` ran the Messages seed case successfully, then failed unrelated Local DB snapshot cases that are outside this PR scope. The scoped Messages seed case passed. + +## Manual Validation Notes +- Browser UI validation could not complete because Playwright Chromium is not installed locally. +- Static and Node validations passed for the PR-specific behavior. +- Code inspection confirmed Messages uses external JS only and the child Emotion picker no longer falls back to global unrelated emotions. +- Runtime service inspection confirmed TTS profile names and filtered emotion sets are returned through the Local API. + +## Changed Files +- `src/dev-runtime/messages/messages-postgres-service.mjs` +- `toolbox/messages/messages.js` +- `toolbox/text-to-speech/text2speech.js` +- `tests/dev-runtime/DbSeedIntegrity.test.mjs` +- `tests/dev-runtime/MessagesPublishValidation.test.mjs` +- `tests/playwright/tools/EventsTool.spec.mjs` +- `tests/playwright/tools/MessagesTool.spec.mjs` +- `tests/tools/MessagesPlaybackSource.test.mjs` +- `tests/tools/Text2SpeechShell.test.mjs` +- `docs_build/dev/reports/PR_26174_BRAVO_020-messages-load-tts-profile-emotions.md` +- `docs_build/dev/reports/codex_changed_files.txt` +- `docs_build/dev/reports/codex_review.diff` + +## ZIP +- `tmp/PR_26174_BRAVO_020-messages-load-tts-profile-emotions_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract.md b/docs_build/dev/reports/PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract.md new file mode 100644 index 000000000..990ea4c6e --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract.md @@ -0,0 +1,65 @@ +# PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract + +## Branch Validation +- PASS: Current branch is `team/BRAVO/messages`. +- PASS: Continued on the existing Bravo branch. +- PASS: Did not merge and did not return to `main`. + +## Summary +- Wired `toolbox/messages/messages.js` directly to the Text To Speech profile contract exported by `toolbox/text-to-speech/text2speech.js`. +- Added a Messages-side adapter that maps TTS contract profile names/emotions to Local API server-owned profile and emotion keys. +- Filtered the Messages parent TTS Profile dropdown to the TTS contract profiles instead of raw Messages API profile rows. +- Filtered child Sentence Emotion dropdowns to the selected parent profile's contract emotions only. +- Guarded the Text To Speech page auto-start so importing the shared contract does not boot the TTS preview UI on the Messages page. + +## Requirement Checklist +- PASS: Messages is wired to the same TTS Profile contract used by Text To Speech. +- PASS: Parent Message dropdown loads TTS Profile names from the TTS contract. +- PASS: Parent selected profile exposes Profile, Gender, Voice, Language, and Age Filter to Messages playback diagnostics. +- PASS: Child Sentence Emotion dropdown loads only emotions from the selected parent profile. +- PASS: `Man Profile 1` exposes `Neutral`, `Calm`, and `Urgent`. +- PASS: `Woman Profile 2` exposes only `Whisper` and `Robot`. +- PASS: Switching parent Profile refreshes child Sentence emotion options after save/reload. +- PASS: Parent Message Play remains routed through selected parent profile plus ordered Sentences. +- PASS: Child Sentence Play remains routed through selected parent profile plus selected Sentence Emotion. +- PASS: Messages runtime does not contain or show `Select an available browser voice before preview.` +- PASS: Text To Speech preview validation was not changed; only the shared export/auto-start guard was adjusted. +- PASS: Messages does not add Emotion Profiles or Voice Profiles sections. +- PASS: No inline styles, inline scripts, inline handlers, or page-local CSS added. +- PASS: No browser-owned authoritative product data or browser-generated authoritative database keys added. + +## Validation Lane Report +- PASS: `node tests/tools/Text2SpeechShell.test.mjs` +- PASS: `node tests/tools/MessagesPlaybackSource.test.mjs` +- PASS: `node --test-name-pattern "Messages Local API seeds" tests/dev-runtime/DbSeedIntegrity.test.mjs` +- PASS: `node tests/dev-runtime/MessagesPublishValidation.test.mjs` +- PASS: Contract/API runtime inspection confirmed matching profile emotion sets: + - `Default Balanced Profile`: `Calm`, `Urgent` + - `Man Profile 1`: `Neutral`, `Calm`, `Urgent` + - `Woman Profile 2`: `Whisper`, `Robot` +- FAIL/BLOCKED: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --project=playwright` + - Blocked before assertions: missing local Chromium executable at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +- PASS: `npm run test:playwright:static` + +## Manual Validation Notes +- Browser UI validation could not complete because the local Playwright Chromium executable is missing. +- Static and Node validations passed for the PR-specific wiring. +- Code inspection confirms Messages imports the TTS contract and no longer assigns raw `voicePayload.ttsProfiles` directly into UI profile state. +- Code inspection confirms Messages playback runtime still has no preview voice validation text. + +## Changed Files +- `src/dev-runtime/messages/messages-postgres-service.mjs` +- `toolbox/messages/messages.js` +- `toolbox/text-to-speech/text2speech.js` +- `tests/dev-runtime/DbSeedIntegrity.test.mjs` +- `tests/dev-runtime/MessagesPublishValidation.test.mjs` +- `tests/playwright/tools/EventsTool.spec.mjs` +- `tests/playwright/tools/MessagesTool.spec.mjs` +- `tests/tools/MessagesPlaybackSource.test.mjs` +- `tests/tools/Text2SpeechShell.test.mjs` +- `docs_build/dev/reports/PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract.md` +- `docs_build/dev/reports/codex_changed_files.txt` +- `docs_build/dev/reports/codex_review.diff` + +## ZIP +- `tmp/PR_26174_BRAVO_021-wire-messages-to-tts-profile-contract_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_BRAVO_022-use-active-tts-profiles-in-messages.md b/docs_build/dev/reports/PR_26174_BRAVO_022-use-active-tts-profiles-in-messages.md new file mode 100644 index 000000000..69904e644 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_BRAVO_022-use-active-tts-profiles-in-messages.md @@ -0,0 +1,51 @@ +# PR_26174_BRAVO_022-use-active-tts-profiles-in-messages + +## Branch Validation +PASS - Current branch is `team/BRAVO/messages`; main was not checked out, merged, or targeted. + +## Summary +- Added a shared Text To Speech profile store/contract helper for active saved TTS profiles. +- Text To Speech now persists its own profile/emotion model for sibling tools. +- Messages now loads saved TTS profiles, syncs profile/emotion names to Local API-owned keys, filters Sentence emotions by selected parent profile, and keeps playback off Text To Speech Local Preview validation. +- Removed the dev-runtime import of the browser Text To Speech UI module and removed separate Message Studio default TTS profile builders. + +## Requirement Checklist +PASS - /toolbox/messages/ loads active saved TTS Profiles from /toolbox/text-to-speech/ via `tts-profile-store.js`. +PASS - Parent Message TTS Profile dropdown is populated from saved active TTS profiles. +PASS - Child Sentence Emotion dropdown is populated only from the selected parent profile's saved emotions. +PASS - Parent Play remains in Message row Actions and resolves ordered Sentences through the selected parent profile. +PASS - Child Play remains in Sentence row Actions and resolves only that Sentence through the parent profile. +PASS - Messages playback does not call Text To Speech Local Preview validation and `toolbox/messages/messages.js` contains no `before preview` text. +PASS - Browser UI module imports were removed from `src/dev-runtime/messages/messages-postgres-service.mjs`. +PASS - Separate Message Studio default TTS profile builders were removed. +PASS - No inline styles, inline scripts, style blocks, or inline handlers were added. +PASS - Local API owns authoritative database keys; Messages syncs saved names to server-created TTS/emotion keys. +PASS - Creator-safe guidance is used for Messages TTS profile load failures. + +## Validation Lane Report +PASS - `node --check toolbox/messages/messages.js`. +PASS - `node --check toolbox/text-to-speech/text2speech.js`. +PASS - `node --check toolbox/text-to-speech/tts-profile-store.js`. +PASS - `node --check src/dev-runtime/messages/messages-postgres-service.mjs`. +PASS - `node --test tests/tools/Text2SpeechShell.test.mjs`. +PASS - `node --test tests/tools/MessagesPlaybackSource.test.mjs`. +PASS - `node --test tests/dev-runtime/MessagesPublishValidation.test.mjs`. +PASS - `node --test --test-name-pattern "Messages Local API" tests/dev-runtime/DbSeedIntegrity.test.mjs`. +BLOCKED - `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs` could not launch because Chromium is not installed at `C:\Users\davidq\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe`. +BLOCKED - Fallback `npm run test:workspace-v2` completed static preflight report generation, then hit the same missing Chromium executable. +KNOWN - Full `tests/dev-runtime/DbSeedIntegrity.test.mjs` still has unrelated Local DB snapshot failures in non-Messages cases. + +## Manual Validation Notes +- Browser manual validation was not completed because Playwright Chromium is missing in this environment. +- Added Playwright coverage for creating/editing a TTS Profile in Text To Speech, opening Messages on the same origin, confirming the saved profile appears, confirming Sentence emotions are profile-filtered, and exercising Parent/Child Play. +- Static grep confirmed Messages no longer contains `Select an available browser voice before preview` or `before preview` wording. + +## Branch Status +- Branch: `team/BRAVO/messages` +- Latest commit: `c45328678` +- Ahead of main: 2 +- Behind main: 0 +- Existing unrelated local edit excluded from PR package: `.gitignore`. + +## ZIP +- Expected ZIP: `tmp/PR_26174_BRAVO_022-use-active-tts-profiles-in-messages_delta.zip` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 15aa8521d..6eccfd73d 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,6 +1,304 @@ -docs_build/dev/BUILD_PR.md -docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md -docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md -docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md -docs_build/dev/reports/codex_review.diff -docs_build/dev/reports/codex_changed_files.txt +# git status --short +M .env.example +M .gitignore +M account/user-controls-page.js +M admin/infrastructure.html +M admin/system-health.html +R toolbox/assets/assets-api-client.js -> assets/js/shared/assets-api-client.js +R toolbox/controls/controls-api-client.js -> assets/js/shared/controls-api-client.js +R toolbox/game-journey/game-journey-api-client.js -> assets/js/shared/game-journey-api-client.js +A assets/js/shared/status.js +R toolbox/text-to-speech/tts-profile-store.js -> assets/js/shared/tts-profile-store.js +M assets/theme-v2/css/status.css +M assets/theme-v2/css/tables.css +M assets/theme-v2/js/admin-infrastructure.js +M assets/theme-v2/js/admin-invitations.js +M assets/theme-v2/js/admin-operations.js +M assets/theme-v2/js/admin-setup-actions.js +M assets/theme-v2/js/admin-system-health.js +M assets/theme-v2/js/gamefoundry-partials.js +A assets/theme-v2/js/toolbox-status-bar.js +R toolbox/assets/assets-upload-worker.js -> assets/toolbox/assets/js/assets-upload-worker.js +R toolbox/assets/assets.js -> assets/toolbox/assets/js/index.js +R toolbox/colors/colors.js -> assets/toolbox/colors/js/index.js +R toolbox/controls/controls.js -> assets/toolbox/controls/js/index.js +R toolbox/events/events.js -> assets/toolbox/events/js/index.js +R toolbox/game-configuration/game-configuration.js -> assets/toolbox/game-configuration/js/index.js +R toolbox/game-design/game-design.js -> assets/toolbox/game-design/js/index.js +R toolbox/game-journey/game-journey.js -> assets/toolbox/game-journey/js/index.js +R toolbox/idea-board/index.js -> assets/toolbox/idea-board/js/index.js +R toolbox/objects/objects.js -> assets/toolbox/objects/js/index.js +R toolbox/tags/tags.js -> assets/toolbox/tags/js/index.js +R toolbox/text-to-speech/text2speech.js -> assets/toolbox/text-to-speech/js/index.js +M docs_build/dev/BUILD_PR.md +M docs_build/dev/ProjectInstructions/PROJECT_INSTRUCTIONS.md +M docs_build/dev/ProjectInstructions/TEAM_START_COMMANDS.md +M docs_build/dev/ProjectInstructions/addendums/multi_team.md +M docs_build/dev/ProjectInstructions/addendums/table_first_ui.md +M docs_build/dev/ProjectInstructions/addendums/team_start_and_release.md +M docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md +M docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md +M docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md +A docs_build/dev/reports/PR_26172_CHARLIE_001-repository-compliance-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_002-test-results-artifact-cleanup.md +A docs_build/dev/reports/PR_26172_CHARLIE_003-src-dev-runtime-test-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move.md +A docs_build/dev/reports/PR_26172_CHARLIE_005-scattered-tool-js-css-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_006-low-risk-tool-asset-migration.md +A docs_build/dev/reports/PR_26172_CHARLIE_006A-game-journey-validation-failure-investigation.md +A docs_build/dev/reports/PR_26172_CHARLIE_007-shared-js-candidate-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_008-canonical-structure-guardrail-script.md +A docs_build/dev/reports/PR_26172_CHARLIE_009-guardrail-preflight-wireup.md +A docs_build/dev/reports/PR_26172_CHARLIE_010-shared-js-low-risk-consolidation.md +A docs_build/dev/reports/PR_26172_CHARLIE_011-test-location-remediation.md +A docs_build/dev/reports/PR_26172_CHARLIE_012-canonical-structure-enforcement-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_013-tool-js-css-canonical-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_014-low-risk-tool-migration-1.md +A docs_build/dev/reports/PR_26172_CHARLIE_015-low-risk-tool-migration-2.md +A docs_build/dev/reports/PR_26172_CHARLIE_016-repository-compliance-reaudit.md +A docs_build/dev/reports/PR_26172_CHARLIE_017-tool-js-css-canonical-migration-audit-2.md +A docs_build/dev/reports/PR_26172_CHARLIE_018-low-risk-tool-migration-3.md +A docs_build/dev/reports/PR_26172_CHARLIE_019-low-risk-tool-migration-4.md +A docs_build/dev/reports/PR_26172_CHARLIE_020-low-risk-tool-migration-5.md +A docs_build/dev/reports/PR_26172_CHARLIE_021-repository-compliance-reaudit-2.md +A docs_build/dev/reports/PR_26172_CHARLIE_022-target-tool-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_023-target-tool-migration-batch-1.md +A docs_build/dev/reports/PR_26172_CHARLIE_024-target-tool-migration-batch-2.md +A docs_build/dev/reports/PR_26172_CHARLIE_025-target-tool-migration-batch-3.md +A docs_build/dev/reports/PR_26172_CHARLIE_026-target-tool-migration-batch-4.md +A docs_build/dev/reports/PR_26172_CHARLIE_027-target-tool-compliance-reaudit.md +A docs_build/dev/reports/PR_26172_CHARLIE_028-remaining-target-tool-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_029-controls-canonical-js-migration.md +A docs_build/dev/reports/PR_26172_CHARLIE_030-assets-tool-canonical-js-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_031-assets-tool-safe-entrypoint-migration.md +A docs_build/dev/reports/PR_26172_CHARLIE_032-assets-worker-api-client-migration-or-exception.md +A docs_build/dev/reports/PR_26172_CHARLIE_033-game-journey-canonical-js-migration-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_034-game-journey-safe-entrypoint-migration.md +A docs_build/dev/reports/PR_26172_CHARLIE_035-game-journey-api-client-migration-or-exception.md +A docs_build/dev/reports/PR_26172_CHARLIE_036-final-target-tool-compliance-reaudit.md +A docs_build/dev/reports/PR_26172_CHARLIE_037-retained-exceptions-audit.md +A docs_build/dev/reports/PR_26172_CHARLIE_038-assets-upload-http-500-investigation.md +A docs_build/dev/reports/PR_26172_CHARLIE_039-assets-api-worker-exception-resolution.md +A docs_build/dev/reports/PR_26172_CHARLIE_040-controls-game-journey-api-client-resolution.md +A docs_build/dev/reports/PR_26172_CHARLIE_041-final-retained-exceptions-reaudit.md +A docs_build/dev/reports/PR_26172_CHARLIE_042-canonical-tool-worker-placement.md +A docs_build/dev/reports/PR_26172_CHARLIE_043-final-charlie-compliance-reaudit.md +A docs_build/dev/reports/PR_26172_OWNER_040-four-team-backlog-alignment.md +A docs_build/dev/reports/PR_26172_OWNER_041-four-team-cleanup.md +A docs_build/dev/reports/PR_26174_ALFA_000-projectinstructions-archive-ignore-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_000-projectinstructions-archive-ignore-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_000-projectinstructions-archive-ignore-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_000-projectinstructions-archive-ignore-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_000-projectinstructions-archive-ignore.md +A docs_build/dev/reports/PR_26174_ALFA_001-idea-board-create-project-api-contract-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_001-idea-board-create-project-api-contract-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_001-idea-board-create-project-api-contract-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_001-idea-board-create-project-api-contract-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_001-idea-board-create-project-api-contract.md +A docs_build/dev/reports/PR_26174_ALFA_002-game-hub-project-intake-display-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_002-game-hub-project-intake-display-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_002-game-hub-project-intake-display-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_002-game-hub-project-intake-display-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_002-game-hub-project-intake-display.md +A docs_build/dev/reports/PR_26174_ALFA_003-game-hub-journey-bootstrap-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_003-game-hub-journey-bootstrap-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_003-game-hub-journey-bootstrap-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_003-game-hub-journey-bootstrap-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_003-game-hub-journey-bootstrap.md +A docs_build/dev/reports/PR_26174_ALFA_004-game-hub-progress-count-model-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_004-game-hub-progress-count-model-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_004-game-hub-progress-count-model-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_004-game-hub-progress-count-model-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_004-game-hub-progress-count-model.md +A docs_build/dev/reports/PR_26174_ALFA_005-idea-project-validation-polish-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_005-idea-project-validation-polish-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_005-idea-project-validation-polish-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_005-idea-project-validation-polish-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_005-idea-project-validation-polish.md +A docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states.md +A docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish.md +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation-final-stack-report.md +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_008-alpha-stack-final-validation.md +A docs_build/dev/reports/PR_26174_ALFA_009-game-hub-parent-child-table-layout-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_009-game-hub-parent-child-table-layout-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_009-game-hub-parent-child-table-layout-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_009-game-hub-parent-child-table-layout-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_009-game-hub-parent-child-table-layout.md +A docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish.md +A docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table.md +A docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation.md +A docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md +A docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center.md +A docs_build/dev/reports/PR_26174_ALFA_015-game-hub-actions-and-setup-cleanup-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_015-game-hub-actions-and-setup-cleanup-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_015-game-hub-actions-and-setup-cleanup-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_015-game-hub-actions-and-setup-cleanup-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_015-game-hub-actions-and-setup-cleanup.md +A docs_build/dev/reports/PR_26174_ALFA_016-game-hub-row-edit-add-selected-state-branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_016-game-hub-row-edit-add-selected-state-manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_016-game-hub-row-edit-add-selected-state-requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_016-game-hub-row-edit-add-selected-state-validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_016-game-hub-row-edit-add-selected-state.md +A docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md +A docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_018-game-selection-button-state.md +A docs_build/dev/reports/PR_26174_ALFA_018-game-selection-button-state_branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_018-game-selection-button-state_manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_018-game-selection-button-state_requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_018-game-selection-button-state_validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_019-game-hub-selected-button-and-crew-label.md +A docs_build/dev/reports/PR_26174_ALFA_019-game-hub-selected-button-and-crew-label_branch-validation.txt +A docs_build/dev/reports/PR_26174_ALFA_019-game-hub-selected-button-and-crew-label_manual-validation-notes.txt +A docs_build/dev/reports/PR_26174_ALFA_019-game-hub-selected-button-and-crew-label_requirement-checklist.txt +A docs_build/dev/reports/PR_26174_ALFA_019-game-hub-selected-button-and-crew-label_validation-lane.txt +A docs_build/dev/reports/PR_26174_ALFA_020-game-hub-idea-board-cleanup_branch-validation.md +A docs_build/dev/reports/PR_26174_ALFA_020-game-hub-idea-board-cleanup_manual-validation-notes.md +A docs_build/dev/reports/PR_26174_ALFA_020-game-hub-idea-board-cleanup_report.md +A docs_build/dev/reports/PR_26174_ALFA_020-game-hub-idea-board-cleanup_requirements-checklist.md +A docs_build/dev/reports/PR_26174_ALFA_020-game-hub-idea-board-cleanup_validation-lane.md +A docs_build/dev/reports/PR_26174_ALFA_021-idea-board-status-filter-table-polish_branch-validation.md +A docs_build/dev/reports/PR_26174_ALFA_021-idea-board-status-filter-table-polish_manual-validation-notes.md +A docs_build/dev/reports/PR_26174_ALFA_021-idea-board-status-filter-table-polish_report.md +A docs_build/dev/reports/PR_26174_ALFA_021-idea-board-status-filter-table-polish_requirements-checklist.md +A docs_build/dev/reports/PR_26174_ALFA_021-idea-board-status-filter-table-polish_validation-lane.md +A docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_branch-validation.md +A docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_manual-validation-notes.md +A docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_report.md +A docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_requirements-checklist.md +A docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_validation-lane.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-final-closeout_branch-validation.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-final-closeout_manual-validation-notes.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-final-closeout_report.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-final-closeout_requirements-checklist.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-final-closeout_validation-lane.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-workstream-closeout_branch-validation.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-workstream-closeout_manual-validation-notes.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-workstream-closeout_report.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-workstream-closeout_requirements-checklist.md +A docs_build/dev/reports/PR_26174_ALFA_EOD-workstream-closeout_validation-lane.md +A docs_build/dev/reports/PR_26174_ALFA_MERGE_PUSH_CLOSEOUT.md +A docs_build/dev/reports/PR_26175_ALFA_001-toolbox-selected-game-status-bar_report.md +A docs_build/dev/reports/PR_26175_ALFA_001-toolbox-selected-game-status-bar_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_001-toolbox-selected-game-status-bar_validation-lane.md +A docs_build/dev/reports/PR_26175_ALFA_002-toolbox-status-bar-context-polish_report.md +A docs_build/dev/reports/PR_26175_ALFA_002-toolbox-status-bar-context-polish_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_002-toolbox-status-bar-context-polish_validation-lane.md +A docs_build/dev/reports/PR_26175_ALFA_004-game-hub-completion-status-audit_report.md +A docs_build/dev/reports/PR_26175_ALFA_004-game-hub-completion-status-audit_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_004-game-hub-completion-status-audit_validation-lane.md +A docs_build/dev/reports/PR_26175_ALFA_005-game-hub-audit-findings-cleanup_report.md +A docs_build/dev/reports/PR_26175_ALFA_005-game-hub-audit-findings-cleanup_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_005-game-hub-audit-findings-cleanup_validation-lane.md +A docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_report.md +A docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_validation-lane.md +A docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md +A docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md +A docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md +A docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard-instruction-compliance-checklist.md +A docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard-manual-validation-notes.md +A docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard.md +A docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard_PLAN.md +A docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization-instruction-compliance-checklist.md +A docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization-manual-validation-notes.md +A docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization.md +A docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization_PLAN.md +A docs_build/dev/reports/PR_26175_CHARLIE_EOD-branch-validation.md +A docs_build/dev/reports/PR_26175_CHARLIE_EOD-closeout.md +A docs_build/dev/reports/PR_26175_CHARLIE_EOD-merge-summary.md +M docs_build/dev/reports/codex_changed_files.txt +M docs_build/dev/reports/codex_review.diff +M package.json +M scripts/run-targeted-test-lanes.mjs +M scripts/validate-browser-env-agnostic.mjs +A scripts/validate-canonical-repository-structure.mjs +M scripts/validate-storage-config.mjs +M src/dev-runtime/persistence/mock-db-store.js +M src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js +M src/dev-runtime/server/local-api-router.mjs +M src/dev-runtime/storage/storage-config.mjs +M src/shared/toolbox/tool-metadata-inventory.js +M tests/dev-runtime/AdminHealthOperations.test.mjs +M tests/dev-runtime/ProductDataProviderContractHardening.test.mjs +A tests/dev-runtime/StorageConfig.test.mjs +R tests/core/FixedTicker.test.mjs -> tests/engine/core/FixedTicker.test.mjs +R tests/core/FrameClock.test.mjs -> tests/engine/core/FrameClock.test.mjs +R tests/core/RuntimeMetrics.test.mjs -> tests/engine/core/RuntimeMetrics.test.mjs +M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +M tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs +M tests/playwright/tools/AssetToolMockRepository.spec.mjs +M tests/playwright/tools/BuildPathProgressSimplification.spec.mjs +M tests/playwright/tools/GameConfigurationMockRepository.spec.mjs +M tests/playwright/tools/GameDesignMockRepository.spec.mjs +M tests/playwright/tools/GameHubMockRepository.spec.mjs +M tests/playwright/tools/GameJourneyTool.spec.mjs +M tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +M tests/playwright/tools/InputMappingV2Tool.spec.mjs +M tests/playwright/tools/ObjectsTool.spec.mjs +M tests/playwright/tools/ToolboxAdminMetadataSsot.spec.mjs +M tests/playwright/tools/ToolboxRoutePages.spec.mjs +A tests/playwright/tools/ToolboxSelectedGameStatusBar.spec.mjs +A tests/regression/CanonicalRepositoryStructureGuardrail.test.mjs +M tests/run-tests.mjs +M tests/tools/MessagesPlaybackSource.test.mjs +M tests/tools/Text2SpeechShell.test.mjs +M toolbox/assets/index.html +M toolbox/colors/index.html +D toolbox/colors/palette-api-client.js +M toolbox/controls/index.html +M toolbox/events/index.html +D toolbox/game-configuration/game-configuration-api-client.js +M toolbox/game-configuration/index.html +D toolbox/game-design/game-design-api-client.js +M toolbox/game-design/index.html +M toolbox/game-hub/game-hub.js +M toolbox/game-hub/index.html +M toolbox/game-journey/index.html +M toolbox/idea-board/index.html +M toolbox/messages/messages.js +M toolbox/objects/index.html +D toolbox/objects/objects-api-client.js +M toolbox/tags/index.html +D toolbox/tags/tags-api-client.js +M toolbox/text-to-speech/index.html + +# git ls-files --others --exclude-standard +(no output) + +# git diff --stat +(no output) \ No newline at end of file diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index a180fc02c..04dbf2e15 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1 +1,36750 @@ -diff --git a/docs_build/dev/BUILD_PR.md b/docs_build/dev/BUILD_PR.mdindex cf7378880..dd5b4d28f 100644--- a/docs_build/dev/BUILD_PR.md+++ b/docs_build/dev/BUILD_PR.md@@ -1,59 +1,63 @@-# PR_26175_ALFA_006-game-hub-create-project-validation+# PR_26175_ALFA_008-game-hub-feature-matrix ## Purpose-Add creator-facing validation for the Game Hub create-project row so blank game names do not silently create fallback projects.+Audit the current Game Hub workflow and publish a feature matrix that maps implemented creator-facing behavior to code and Playwright evidence. ## Source Of Truth-This `BUILD_PR.md` is the source of truth for `PR_26175_ALFA_006-game-hub-create-project-validation`.+This `BUILD_PR.md` is the source of truth for `PR_26175_ALFA_008-game-hub-feature-matrix`. ## Exact Scope-- Validate the Game Hub add-game row before calling the repository create method.-- Block signed-in creator saves when the game name is blank or whitespace-only.-- Keep the add-game row open after validation failure.-- Show a creator-safe validation message in the existing Game Hub status log.-- Mark the game name input invalid for accessibility.-- Preserve valid create/open/delete behavior.-- Preserve guest save redirect behavior.+- Produce a Game Hub feature matrix only.+- Audit Game Hub table workflow, selected/open game behavior, create/edit/delete actions, child tables, guest save gating, empty/error states, Theme V2 layout, and targeted Game Hub coverage.+- Use current `main` behavior as evidence.+- Preserve Game Hub UI/product behavior. - Preserve API/service/repository contracts.-- Add targeted Playwright coverage for the create validation path.+- Preserve previous ALFA Game Hub cleanup and create-validation behavior.+- Do not implement product/UI changes unless validation exposes a requirement-critical defect. ## Exact Targets - `docs_build/dev/BUILD_PR.md`-- `toolbox/game-hub/game-hub.js`-- `tests/playwright/tools/GameHubMockRepository.spec.mjs`-- `docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_report.md`-- `docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_validation-lane.md`-- `docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_requirements-checklist.md`+- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md`+- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md`+- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md` - `docs_build/dev/reports/codex_review.diff` - `docs_build/dev/reports/codex_changed_files.txt` +## Evidence Sources+- `toolbox/game-hub/index.html`+- `toolbox/project-workspace/index.html`+- `toolbox/game-hub/game-hub.js`+- `toolbox/game-hub/game-hub-api-client.js`+- `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js`+- `tests/playwright/tools/GameHubMockRepository.spec.mjs`+ ## Out Of Scope-- No repository/API/service contract changes.-- No Game Journey completion-metrics changes.+- No Game Hub product or UI changes.+- No Game Journey changes. - No shared toolbox status bar changes.-- No unrelated Game Hub workflow changes. - No browser-owned product data as source of truth.-- No silent create-name fallback in the Game Hub page flow.+- No API/service/repository contract changes. - No inline styles, style blocks, or page-local CSS. - No engine core changes. - No `start_of_day` folder changes.+- No ALFA_007 work. ## Validation-Run targeted create-project validation:+Run targeted Game Hub validation: ```powershell-npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --workers=1 --grep "Game Hub creates, opens, and deletes mock games"+npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --workers=1 ``` -Also verify changed source does not introduce inline styles or style blocks:+Also verify changed docs/reports do not introduce inline styles or style blocks: ```powershell-rg -n "<[s]tyle|[s]tyle=" toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs docs_build/dev/BUILD_PR.md docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_report.md docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_validation-lane.md docs_build/dev/reports/PR_26175_ALFA_006-game-hub-create-project-validation_requirements-checklist.md+rg -n "<[s]tyle|[s]tyle=" docs_build/dev/BUILD_PR.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md ``` ## Artifact Create repo-structured delta ZIP: ```text-tmp/PR_26175_ALFA_006-game-hub-create-project-validation_delta.zip+tmp/PR_26175_ALFA_008-game-hub-feature-matrix_delta.zip ```diff --git a/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.mdnew file mode 100644index 000000000..3fbcfdb04--- /dev/null+++ b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md@@ -0,0 +1,35 @@+# PR_26175_ALFA_008-game-hub-feature-matrix Report++## Overall Status+PASS++## Summary+ALFA_008 produced a report-only Game Hub feature matrix from current `main` behavior. No product/UI/source implementation changes were needed. Targeted Game Hub validation passed with 14/14 Playwright tests.++## Feature Matrix+| Feature Area | Status | Current Behavior | Source Evidence | Validation Evidence |+| --- | --- | --- | --- | --- |+| Deprecated project workspace route | PASS | Older Project Workspace links land on a Game Hub handoff page and expose one creator-facing Game Hub action. | `toolbox/project-workspace/index.html:17`, `toolbox/project-workspace/index.html:20`, `toolbox/project-workspace/index.html:21`, `toolbox/project-workspace/index.html:22` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:244`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:248`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:251` |+| Game Hub page shell | PASS | Game Hub uses Theme V2 shared classes, a wide tool workspace, center Games panel, and shared status elements. | `toolbox/game-hub/index.html:24`, `toolbox/game-hub/index.html:25`, `toolbox/game-hub/index.html:26`, `toolbox/game-hub/index.html:36`, `toolbox/game-hub/index.html:37`, `toolbox/game-hub/index.html:39` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:263`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:873`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:878`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:880` |+| Game table ownership | PASS | The Game Hub page owns the creator-facing game table and renders it in the center panel only. | `toolbox/game-hub/game-hub.js:538`, `toolbox/game-hub/game-hub.js:561`, `toolbox/game-hub/game-hub.js:563`, `toolbox/game-hub/game-hub.js:567`, `toolbox/game-hub/game-hub.js:569` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:288`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:289`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:296`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:298` |+| Parent row display | PASS | Parent rows display Game, Purpose, Status, and Actions without owner/role/next-tool columns. | `toolbox/game-hub/game-hub.js:500`, `toolbox/game-hub/game-hub.js:515`, `toolbox/game-hub/game-hub.js:518`, `toolbox/game-hub/game-hub.js:519`, `toolbox/game-hub/game-hub.js:523` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:298`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:304`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:305`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:306`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:307` |+| Selected/open game affordance | PASS | Selection is represented by the game toggle button only; rows do not gain active-row attributes or row highlighting. | `toolbox/game-hub/game-hub.js:500`, `toolbox/game-hub/game-hub.js:515`, `toolbox/game-hub/game-hub.js:768`, `toolbox/game-hub/game-hub.js:771`, `toolbox/game-hub/game-hub.js:776` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:308`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:311`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:314`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:324`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:326` |+| Add game workflow | PASS | Creators can open an add row, enter Game/Purpose/Status, save a new game, and cancel without saving. | `toolbox/game-hub/game-hub.js:428`, `toolbox/game-hub/game-hub.js:441`, `toolbox/game-hub/game-hub.js:448`, `toolbox/game-hub/game-hub.js:451`, `toolbox/game-hub/game-hub.js:699`, `toolbox/game-hub/game-hub.js:707` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:379`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:398`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:401`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:411`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:426`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:430` |+| Create-name validation | PASS | Blank and whitespace-only names are blocked in the page flow before repository create, keeping the add row open and showing a creator-safe status message. | `toolbox/game-hub/game-hub.js:681`, `toolbox/game-hub/game-hub.js:684`, `toolbox/game-hub/game-hub.js:687`, `toolbox/game-hub/game-hub.js:690`, `toolbox/game-hub/game-hub.js:703`, `toolbox/game-hub/game-hub.js:707` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:386`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:389`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:391`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:392`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:393`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:397` |+| Repository contract preservation | PASS | The Game Hub API client still uses the shared server repository client, and the mock repository contract remains unchanged. The lower-level repository still has defensive defaults, but ALFA_006 page validation prevents blank names from becoming creator-facing Game Hub creates. | `toolbox/game-hub/game-hub-api-client.js:1`, `toolbox/game-hub/game-hub-api-client.js:15`, `toolbox/game-hub/game-hub-api-client.js:16`, `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js:378`, `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js:381`, `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js:419` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:391`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:392`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:396`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:397` |+| Edit game purpose/status | PASS | Existing game names are read-only in edit mode while Purpose and Status remain editable. | `toolbox/game-hub/game-hub.js:464`, `toolbox/game-hub/game-hub.js:470`, `toolbox/game-hub/game-hub.js:471`, `toolbox/game-hub/game-hub.js:475`, `toolbox/game-hub/game-hub.js:478`, `toolbox/game-hub/game-hub.js:727`, `toolbox/game-hub/game-hub.js:741`, `toolbox/game-hub/game-hub.js:752` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:413`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:417`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:418`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:419`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:424`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:780` |+| Delete open game | PASS | Signed-in creators can delete the active non-source-linked game, and source-linked games are protected. | `toolbox/game-hub/game-hub.js:834`, `toolbox/game-hub/game-hub.js:838`, `toolbox/game-hub/game-hub.js:845`, `toolbox/game-hub/game-hub.js:851`, `toolbox/game-hub/game-hub.js:852` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:451`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:452`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:454`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:860`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:861` |+| Source idea child table | PASS | Source-linked games can expose read-only Idea Board context without edit/delete controls. | `toolbox/game-hub/game-hub.js:399`, `toolbox/game-hub/game-hub.js:401`, `toolbox/game-hub/game-hub.js:404`, `toolbox/game-hub/game-hub.js:415`, `toolbox/game-hub/game-hub.js:422` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:462`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:559`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:563`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:568`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:576` |+| Readiness output child table | PASS | Every expanded game can expose readiness output and checklist context from Game Journey progress data. | `toolbox/game-hub/game-hub.js:353`, `toolbox/game-hub/game-hub.js:370`, `toolbox/game-hub/game-hub.js:372`, `toolbox/game-hub/game-hub.js:385`, `toolbox/game-hub/game-hub.js:386`, `toolbox/game-hub/game-hub.js:408` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:362`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:365`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:579`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:581`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:826`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:858` |+| Guest browsing/save gating | PASS | Guests can browse Game Hub but save operations redirect to sign-in and delete is disabled. | `toolbox/game-hub/game-hub.js:131`, `toolbox/game-hub/game-hub.js:139`, `toolbox/game-hub/game-hub.js:147`, `toolbox/game-hub/game-hub.js:151`, `toolbox/game-hub/game-hub.js:174`, `toolbox/game-hub/game-hub.js:178` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:593`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:601`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:603`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:604`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:617`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:623` |+| Empty state | PASS | Empty project lists show a creator-safe prompt and keep Add Game available. | `toolbox/game-hub/game-hub.js:557`, `toolbox/game-hub/game-hub.js:558`, `toolbox/game-hub/game-hub.js:570` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:633`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:680`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:687`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:688`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:690` |+| Unavailable/error states | PASS | Repository and active-game errors use creator-safe messages without leaking backend details. | `toolbox/game-hub/game-hub.js:66`, `toolbox/game-hub/game-hub.js:70`, `toolbox/game-hub/game-hub.js:82`, `toolbox/game-hub/game-hub.js:89`, `toolbox/game-hub/game-hub.js:549`, `toolbox/game-hub/game-hub.js:550` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:697`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:713`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:716`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:724`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:749`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:771` |+| Role-focused Toolbox discovery | PASS | Toolbox role filters still include Game Hub where expected while hiding unavailable role-focused tools. | `tests/playwright/tools/GameHubMockRepository.spec.mjs:996`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1000`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1002`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1010`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1012`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1030` | `tests/playwright/tools/GameHubMockRepository.spec.mjs:996`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1012`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1024`, `tests/playwright/tools/GameHubMockRepository.spec.mjs:1034` |++## Validation+- PASS: `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --workers=1`+- Result: 14 passed, 0 failed.+- PASS: Inline-style/style-block report scan completed with no matches after reports were created.++## Artifact+- `tmp/PR_26175_ALFA_008-game-hub-feature-matrix_delta.zip`diff --git a/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.mdnew file mode 100644index 000000000..25f65acd6--- /dev/null+++ b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md@@ -0,0 +1,15 @@+# PR_26175_ALFA_008-game-hub-feature-matrix Requirements Checklist++| Requirement | Status | Evidence |+| --- | --- | --- |+| Replace stale BUILD source of truth with ALFA_008 | PASS | `docs_build/dev/BUILD_PR.md:1` identifies `PR_26175_ALFA_008-game-hub-feature-matrix`. |+| Produce a Game Hub feature matrix only | PASS | `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md` contains the feature matrix and no product source files were changed. |+| Audit Game Hub table workflow completion | PASS | Matrix rows cover Game table ownership, parent rows, selection affordance, add/create validation, edit, delete, child tables, guest gating, and error states. |+| Use current main behavior as evidence | PASS | Matrix evidence points to `toolbox/game-hub/index.html`, `toolbox/project-workspace/index.html`, `toolbox/game-hub/game-hub.js`, `toolbox/game-hub/game-hub-api-client.js`, `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js`, and `tests/playwright/tools/GameHubMockRepository.spec.mjs`. |+| Preserve Game Hub UI/product behavior | PASS | No Game Hub product/UI/source implementation files were modified in this PR. |+| Preserve API/service/repository contracts | PASS | No API, service, or repository files were modified. Matrix records existing API/repository evidence without changing contracts. |+| Preserve previous ALFA cleanup and create validation behavior | PASS | Targeted Game Hub spec passed 14/14 tests, including ALFA_005 selector cleanup and ALFA_006 create-name validation assertions. |+| Do not add browser-owned product data as source of truth | PASS | No runtime/product JSON or browser-owned data contract files were modified. |+| Do not add inline styles, style blocks, or page-local CSS | PASS | ALFA_008 changes are docs/reports only, and the targeted style scan found no matches. |+| Create required reports | PASS | Report, validation lane, requirements checklist, review diff, and changed-files report are included. |+| Create repo-structured delta ZIP | PASS | `tmp/PR_26175_ALFA_008-game-hub-feature-matrix_delta.zip` was created after reports were finalized. |diff --git a/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.mdnew file mode 100644index 000000000..71a5ec416--- /dev/null+++ b/docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md@@ -0,0 +1,11 @@+# PR_26175_ALFA_008-game-hub-feature-matrix Validation Lane++## Commands+| Command | Status | Evidence |+| --- | --- | --- |+| `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --workers=1` | PASS | 14 passed, 0 failed. Covered deprecated route, create/open/delete, parent/child tables, guest save gating, empty state, unavailable state, active-game error state, malformed active-game payloads, purpose/status edits, readiness rows, wide Theme V2 layout, representative toolbox layout, Learn guidance, and member-role filters. |+| `rg -n "<[s]tyle|[s]tyle=" docs_build/dev/BUILD_PR.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md` | PASS | No inline style or style block matches in ALFA_008 changed docs/reports. |++## Notes+- Playwright updated shared coverage report outputs during validation; those generated files were restored because they are outside ALFA_008 exact targets.+- No product/UI/source implementation files were changed for this audit.diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txtindex 9dca38cd8..15aa8521d 100644--- a/docs_build/dev/reports/codex_changed_files.txt+++ b/docs_build/dev/reports/codex_changed_files.txt@@ -1,23 +1,6 @@-?.env.example-admin/infrastructure.html-admin/system-health.html-assets/theme-v2/js/admin-infrastructure.js-assets/theme-v2/js/admin-system-health.js-docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard.md-docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard_PLAN.md-docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard-instruction-compliance-checklist.md-docs_build/dev/reports/PR_26175_CHARLIE_002-system-health-dashboard-manual-validation-notes.md-docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization.md-docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization_PLAN.md-docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization-instruction-compliance-checklist.md-docs_build/dev/reports/PR_26175_CHARLIE_003-r2-storage-standardization-manual-validation-notes.md-docs_build/dev/reports/PR_26175_CHARLIE_EOD-branch-validation.md-docs_build/dev/reports/PR_26175_CHARLIE_EOD-closeout.md-docs_build/dev/reports/PR_26175_CHARLIE_EOD-merge-summary.md-scripts/validate-storage-config.mjs-src/dev-runtime/server/local-api-router.mjs-src/dev-runtime/storage/storage-config.mjs-tests/dev-runtime/AdminHealthOperations.test.mjs-tests/dev-runtime/StorageConfig.test.mjs-tests/playwright/tools/AdminHealthOperationsPage.spec.mjs-tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs+docs_build/dev/BUILD_PR.md+docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md+docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md+docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md+docs_build/dev/reports/codex_review.diff+docs_build/dev/reports/codex_changed_files.txt \ No newline at end of file +diff --git a/.env.example b/.env.example +index f293b6f83..9d5e4301b 100644 +--- a/.env.example ++++ b/.env.example +@@ -50,6 +50,11 @@ GAMEFOUNDRY_DB_BACKUP_DIR= + + # Server-only project asset storage configuration. + # Browser uploads must go through the server API and must not receive these secrets. ++# Approved GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX values: ++# DEV /dev/projects/ ++# IST /ist/projects/ ++# UAT /uat/projects/ ++# PRD /prod/projects/ + GAMEFOUNDRY_STORAGE_ENDPOINT= + GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID= + +diff --git a/.gitignore b/.gitignore +index ec5f65503..8f4247376 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -13,10 +13,12 @@ + # Ignore the NEXT_COMMAND.txt file + NEXT_COMMAND.txt + +-# Ignore the tests results and temporary files ++# Ignore generated dependency, test result, and temporary files + node_modules/ + tests/results/ ++tests/results/** + tmp/test-results/ ++tmp/test-results/** + tmp/ + scripts/untracked/ + projects/ +diff --git a/account/user-controls-page.js b/account/user-controls-page.js +index 02fb2c117..d42be3309 100644 +--- a/account/user-controls-page.js ++++ b/account/user-controls-page.js +@@ -1,4 +1,4 @@ +-import { createControlsToolApiRepository } from "../toolbox/controls/controls-api-client.js"; ++import { createControlsToolApiRepository } from "../assets/js/shared/controls-api-client.js"; + import { getSessionCurrent } from "../src/api/session-api-client.js"; + import InputService from "../src/engine/input/InputService.js"; + import InputCaptureService from "../src/engine/input/InputCaptureService.js"; +diff --git a/admin/infrastructure.html b/admin/infrastructure.html +index 0c9e90b3e..89bf99e0d 100644 +--- a/admin/infrastructure.html ++++ b/admin/infrastructure.html +@@ -41,7 +41,7 @@ +

/dev/projects/

+

/ist/projects/

+

/uat/projects/

+-

/prd/projects/

++

/prod/projects/

+ + + +@@ -81,7 +81,7 @@ + DEV/dev/projects/Loading + IST/ist/projects/Loading + UAT/uat/projects/Loading +- PRD/prd/projects/Loading ++ PRD/prod/projects/Loading + + + +diff --git a/admin/system-health.html b/admin/system-health.html +index f5330bcdd..65ff3c12e 100644 +--- a/admin/system-health.html ++++ b/admin/system-health.html +@@ -39,6 +39,7 @@ + Health Sections +
+

Environment Summary

++

Local API Startup

+

Database Health

+

Storage Health

+

Runtime Environment

+@@ -74,6 +75,21 @@ + + +
++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
Local API Startup Diagnostics
FieldSafe ValueStatus
Startup diagnosticsWaiting for safe API statusPENDING
++
+
+ + +diff --git a/toolbox/assets/assets-api-client.js b/assets/js/shared/assets-api-client.js +similarity index 95% +rename from toolbox/assets/assets-api-client.js +rename to assets/js/shared/assets-api-client.js +index 9e504899c..1232aac7c 100644 +--- a/toolbox/assets/assets-api-client.js ++++ b/assets/js/shared/assets-api-client.js +@@ -3,7 +3,7 @@ import { + createServerRepositoryClient, + readServerToolConstants, + requireServerConstant, +-} from "../../src/api/server-api-client.js"; ++} from "../../../src/api/server-api-client.js"; + + const constants = readServerToolConstants("assets"); + +diff --git a/toolbox/controls/controls-api-client.js b/assets/js/shared/controls-api-client.js +similarity index 96% +rename from toolbox/controls/controls-api-client.js +rename to assets/js/shared/controls-api-client.js +index 89eb688be..3b382022b 100644 +--- a/toolbox/controls/controls-api-client.js ++++ b/assets/js/shared/controls-api-client.js +@@ -2,7 +2,7 @@ import { + createServerRepositoryClient, + readServerToolConstants, + requireServerConstant, +-} from "../../src/api/server-api-client.js"; ++} from "../../../src/api/server-api-client.js"; + + const constants = readServerToolConstants("controls"); + +diff --git a/toolbox/game-journey/game-journey-api-client.js b/assets/js/shared/game-journey-api-client.js +similarity index 91% +rename from toolbox/game-journey/game-journey-api-client.js +rename to assets/js/shared/game-journey-api-client.js +index 4d9647bb2..fba67b725 100644 +--- a/toolbox/game-journey/game-journey-api-client.js ++++ b/assets/js/shared/game-journey-api-client.js +@@ -2,11 +2,11 @@ import { + createServerRepositoryClient, + readServerToolConstants, + requireServerConstant, +-} from "../../src/api/server-api-client.js"; ++} from "../../../src/api/server-api-client.js"; + export { + readGameJourneyCompletionMetrics, + updateGameJourneyCompletionMetric, +-} from "../../src/api/game-journey-completion-api-client.js"; ++} from "../../../src/api/game-journey-completion-api-client.js"; + + const constants = readServerToolConstants("game-journey"); + +diff --git a/assets/js/shared/status.js b/assets/js/shared/status.js +new file mode 100644 +index 000000000..26bb6926f +--- /dev/null ++++ b/assets/js/shared/status.js +@@ -0,0 +1,42 @@ ++export const STATUS_VALUES = Object.freeze(["PASS", "WARN", "FAIL", "PENDING", "INFO", "SKIP"]); ++ ++export function statusText(value, fallback = "not available") { ++ const text = String(value ?? "").trim(); ++ return text || fallback; ++} ++ ++export function normalizeStatusValue(value, fallback = "PENDING") { ++ const normalized = String(value || "").trim().toUpperCase(); ++ return STATUS_VALUES.includes(normalized) ? normalized : fallback; ++} ++ ++export function formatStatusMessage(status, message, options = {}) { ++ const normalized = normalizeStatusValue(status, options.fallbackStatus || "PENDING"); ++ const resolvedMessage = Object.hasOwn(options, "fallbackMessage") ++ ? statusText(message, options.fallbackMessage) ++ : String(message); ++ return `${normalized}: ${resolvedMessage}`; ++} ++ ++export function formatStatusReason(status, reason, options = {}) { ++ const normalized = normalizeStatusValue(status, options.fallbackStatus || "PENDING"); ++ return `${normalized}: ${statusText(reason, options.fallbackReason || "Safe server diagnostics did not provide a reason.")}`; ++} ++ ++export function applyStatusNode(node, status, options = {}) { ++ if (!node) { ++ return ""; ++ } ++ const normalized = normalizeStatusValue(status, options.fallbackStatus || "PENDING"); ++ node.textContent = normalized; ++ node.dataset.healthStatus = normalized; ++ if (normalized === "PASS" && !options.reason) { ++ node.removeAttribute("title"); ++ node.removeAttribute("aria-label"); ++ return normalized; ++ } ++ const reason = statusText(options.reason, options.titleFallback || "Safe server diagnostics returned this non-PASS status."); ++ node.setAttribute("title", `${options.titlePrefix || "Reason: "}${reason}`); ++ node.setAttribute("aria-label", formatStatusReason(normalized, options.reason, options)); ++ return normalized; ++} +diff --git a/toolbox/text-to-speech/tts-profile-store.js b/assets/js/shared/tts-profile-store.js +similarity index 100% +rename from toolbox/text-to-speech/tts-profile-store.js +rename to assets/js/shared/tts-profile-store.js +diff --git a/assets/theme-v2/css/status.css b/assets/theme-v2/css/status.css +index 5c521c45d..88e0d5e64 100644 +--- a/assets/theme-v2/css/status.css ++++ b/assets/theme-v2/css/status.css +@@ -35,6 +35,119 @@ + line-height: var(--line-height-copy) + } + ++.toolbox-status-bar { ++ width: 100%; ++ border-block: var(--border-standard); ++ background: var(--panel-overlay-strong); ++ color: var(--text) ++} ++ ++.toolbox-status-bar__inner { ++ width: var(--container-width); ++ margin: var(--space-0) auto; ++ padding: var(--space-10) var(--space-0); ++ display: grid; ++ grid-template-columns: minmax(var(--space-0), 1fr) minmax(var(--space-0), 2fr); ++ gap: var(--space-16); ++ align-items: center ++} ++ ++.toolbox-status-bar__game { ++ min-width: var(--space-0); ++ display: flex; ++ align-items: center; ++ flex-wrap: wrap; ++ gap: var(--space-14); ++ text-align: left ++} ++ ++.toolbox-status-bar__field { ++ min-width: var(--space-0); ++ display: grid; ++ gap: var(--space-3) ++} ++ ++.toolbox-status-bar__label { ++ color: var(--muted); ++ font-size: var(--font-size-xs); ++ font-weight: var(--font-weight-heavy); ++ letter-spacing: var(--letter-spacing-kicker); ++ text-transform: uppercase ++} ++ ++.toolbox-status-bar__game-name { ++ color: var(--text); ++ font-size: var(--font-size-base); ++ overflow-wrap: anywhere ++} ++ ++.toolbox-status-bar__purpose { ++ color: var(--muted); ++ font-size: var(--font-size-sm); ++ overflow-wrap: anywhere ++} ++ ++.toolbox-status-bar__center { ++ min-width: var(--space-0); ++ display: flex; ++ align-items: center; ++ justify-content: center; ++ flex-wrap: wrap; ++ gap: var(--space-10); ++ text-align: center ++} ++ ++.toolbox-status-bar__context-type { ++ flex: 0 0 auto ++} ++ ++.toolbox-status-bar__message { ++ margin: var(--space-0); ++ max-width: var(--measure-lg); ++ overflow-wrap: anywhere ++} ++ ++.toolbox-status-bar__action { ++ flex: 0 0 auto ++} ++ ++.toolbox-status-bar[data-selected-game-state="active"] { ++ border-color: color-mix(in srgb, var(--green) 52%, var(--line)) ++} ++ ++.toolbox-status-bar[data-selected-game-state="missing"] { ++ border-color: var(--gold-border-muted) ++} ++ ++.toolbox-status-bar[data-selected-game-state="error"] { ++ border-color: color-mix(in srgb, var(--red) 52%, var(--line)) ++} ++ ++.toolbox-status-bar[data-toolbox-status-context-kind="error"] .toolbox-status-bar__context-type { ++ border-color: color-mix(in srgb, var(--red) 62%, var(--line)); ++ color: var(--red) ++} ++ ++.toolbox-status-bar[data-toolbox-status-context-kind="warning"] .toolbox-status-bar__context-type, ++.toolbox-status-bar[data-toolbox-status-context-kind="validation"] .toolbox-status-bar__context-type { ++ border-color: var(--gold-border-muted); ++ color: var(--gold) ++} ++ ++.toolbox-status-bar[data-toolbox-status-context-kind="save"] .toolbox-status-bar__context-type { ++ border-color: color-mix(in srgb, var(--green) 62%, var(--line)); ++ color: var(--green) ++} ++ ++body.tool-focus-mode .toolbox-status-bar { ++ position: fixed; ++ inset-block-end: var(--space-0); ++ inset-inline: var(--space-0); ++ z-index: var(--z-index-lg); ++ border-block-end: var(--space-0); ++ box-shadow: var(--shadow-md) ++} ++ + .platform-banner { + width: 100%; + border-bottom: var(--border-standard); +@@ -144,3 +257,16 @@ + color: var(--bg); + text-shadow: none + } ++ ++@media (max-width: 720px) { ++ .toolbox-status-bar__inner { ++ width: var(--container-width); ++ grid-template-columns: 1fr; ++ text-align: center ++ } ++ ++ .toolbox-status-bar__game { ++ justify-content: center; ++ text-align: center ++ } ++} +diff --git a/assets/theme-v2/css/tables.css b/assets/theme-v2/css/tables.css +index c39ef19f0..5575ff684 100644 +--- a/assets/theme-v2/css/tables.css ++++ b/assets/theme-v2/css/tables.css +@@ -116,26 +116,26 @@ td { + } + + .idea-board-idea-label { +- display: inline-flex; +- align-items: center; +- gap: .35em; ++ display: inline; + color: inherit; + font: inherit; + line-height: var(--line-height-single); + vertical-align: baseline; +- white-space: nowrap ++ white-space: normal + } + + .idea-board-idea-label__text { +- line-height: var(--line-height-single) ++ overflow-wrap: anywhere; ++ line-height: inherit + } + + .idea-board-idea-chevron { + display: inline-block; + width: 1em; + height: 1em; +- flex: 0 0 1em; ++ margin-right: .35em; + background: currentColor; ++ vertical-align: -0.125em; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; +diff --git a/assets/theme-v2/js/admin-infrastructure.js b/assets/theme-v2/js/admin-infrastructure.js +index 0251c32d6..b470c84df 100644 +--- a/assets/theme-v2/js/admin-infrastructure.js ++++ b/assets/theme-v2/js/admin-infrastructure.js +@@ -6,7 +6,7 @@ const STORAGE_PATH_LANES = Object.freeze([ + Object.freeze({ lane: "DEV", path: "/dev/projects/" }), + Object.freeze({ lane: "IST", path: "/ist/projects/" }), + Object.freeze({ lane: "UAT", path: "/uat/projects/" }), +- Object.freeze({ lane: "PRD", path: "/prd/projects/" }), ++ Object.freeze({ lane: "PRD", path: "/prod/projects/" }), + ]); + + class AdminInfrastructureStoragePathStatus { +diff --git a/assets/theme-v2/js/admin-invitations.js b/assets/theme-v2/js/admin-invitations.js +index 5299093f3..3e3c9e829 100644 +--- a/assets/theme-v2/js/admin-invitations.js ++++ b/assets/theme-v2/js/admin-invitations.js +@@ -3,6 +3,7 @@ import { + readAdminInvites, + revokeAdminBetaInvite + } from "../../../src/api/admin-invitations-api-client.js"; ++import { formatStatusMessage } from "../../js/shared/status.js"; + + function text(value) { + if (value === undefined || value === null || value === "") { +@@ -53,7 +54,7 @@ class AdminInvitesController { + } + + setStatus(status, message) { +- this.status.textContent = `${status}: ${message}`; ++ this.status.textContent = formatStatusMessage(status, message); + } + + setSummary(payload = {}) { +diff --git a/assets/theme-v2/js/admin-operations.js b/assets/theme-v2/js/admin-operations.js +index 306faca9b..fd669562f 100644 +--- a/assets/theme-v2/js/admin-operations.js ++++ b/assets/theme-v2/js/admin-operations.js +@@ -2,6 +2,7 @@ import { + readAdminOperationsStatus, + runAdminOperationAction + } from "../../../src/api/admin-operations-api-client.js"; ++import { formatStatusMessage } from "../../js/shared/status.js"; + + class AdminOperationsController { + constructor(root) { +@@ -25,7 +26,7 @@ class AdminOperationsController { + } + + setStatus(status, message) { +- this.status.textContent = `${status}: ${message}`; ++ this.status.textContent = formatStatusMessage(status, message); + } + + createLabeledControl(labelText, control) { +diff --git a/assets/theme-v2/js/admin-setup-actions.js b/assets/theme-v2/js/admin-setup-actions.js +index 27ddbd1aa..84f39fcd9 100644 +--- a/assets/theme-v2/js/admin-setup-actions.js ++++ b/assets/theme-v2/js/admin-setup-actions.js +@@ -1,11 +1,12 @@ + import { readAdminSetupStatus } from "../../../src/api/admin-setup-api-client.js"; ++import { formatStatusMessage } from "../../js/shared/status.js"; + + const refreshButtons = Array.from(document.querySelectorAll("[data-admin-setup-refresh]")); + const statusFields = Array.from(document.querySelectorAll("[data-admin-setup-status]")); + const statusRows = Array.from(document.querySelectorAll("[data-admin-setup-status-rows]")); + + function setStatus(message, status = "PASS") { +- const text = `${status}: ${message}`; ++ const text = formatStatusMessage(status, message); + statusFields.forEach((field) => { + field.textContent = text; + }); +diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js +index dfe67df79..72ddd7ea8 100644 +--- a/assets/theme-v2/js/admin-system-health.js ++++ b/assets/theme-v2/js/admin-system-health.js +@@ -2,8 +2,12 @@ import { + readAdminSystemHealthStatus, + runAdminSystemHealthStorageConnectivityAction, + } from "../../../src/api/admin-system-health-api-client.js"; ++import { ++ applyStatusNode, ++ normalizeStatusValue, ++ statusText, ++} from "../../js/shared/status.js"; + +-const STATUS_VALUES = Object.freeze(["PASS", "WARN", "FAIL", "PENDING", "INFO", "SKIP"]); + const STORAGE_DIAGNOSTIC_ACTIONS = Object.freeze([ + Object.freeze({ actionId: "storage-list", key: "list" }), + Object.freeze({ actionId: "storage-write-test-object", key: "write" }), +@@ -12,18 +16,7 @@ const STORAGE_DIAGNOSTIC_ACTIONS = Object.freeze([ + ]); + + function asText(value, fallback = "not available") { +- const text = String(value ?? "").trim(); +- return text || fallback; +-} +- +-function statusValue(value, fallback = "PENDING") { +- const normalized = String(value || "").trim().toUpperCase(); +- return STATUS_VALUES.includes(normalized) ? normalized : fallback; +-} +- +-function reasonText(status, reason) { +- const message = asText(reason, "Safe server diagnostics did not provide a reason."); +- return `${status}: ${message}`; ++ return statusText(value, fallback); + } + + class AdminSystemHealthController { +@@ -45,6 +38,7 @@ class AdminSystemHealthController { + node.dataset.adminSystemHealthStorageStatus, + node, + ])); ++ this.startupRows = root.querySelector("[data-admin-system-health-startup-rows]"); + this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]"); + } + +@@ -80,26 +74,14 @@ class AdminSystemHealthController { + } + + setStatusNode(node, status, reason = "") { +- if (!node) { +- return; +- } +- const normalized = statusValue(status); +- node.textContent = normalized; +- node.dataset.healthStatus = normalized; +- if (normalized === "PASS" && !reason) { +- node.removeAttribute("title"); +- node.removeAttribute("aria-label"); +- return; +- } +- const resolvedReason = reasonText(normalized, reason); +- node.setAttribute("title", `Reason: ${asText(reason, "Safe server diagnostics returned this non-PASS status.")}`); +- node.setAttribute("aria-label", resolvedReason); ++ applyStatusNode(node, status, { reason }); + } + + renderPending(reason) { + ["host", "database", "migration", "connection"].forEach((key) => { + this.setStatus(key, "PENDING", reason); + }); ++ this.renderStartupPending(reason); + this.renderStoragePending(reason); + } + +@@ -138,6 +120,45 @@ class AdminSystemHealthController { + this.setStorageStatus("bucket", storageStatus.bucketStatus || storageStatus.status, reason); + } + ++ renderStartupPending(reason) { ++ if (!this.startupRows) { ++ return; ++ } ++ const row = document.createElement("tr"); ++ row.append( ++ this.createCell("Local API startup diagnostics"), ++ this.createCell("not available"), ++ this.createStatusCell("PENDING", reason), ++ ); ++ this.startupRows.replaceChildren(row); ++ } ++ ++ renderStartupDiagnostics(localApiStartup = {}) { ++ if (!this.startupRows) { ++ return; ++ } ++ if (localApiStartup?.secretsExposed === true || localApiStartup?.secretEditingAllowed === true) { ++ this.renderStartupPending("Safe Local API startup diagnostics were blocked because the response exposed secret controls."); ++ return; ++ } ++ const rows = Array.isArray(localApiStartup.rows) ? localApiStartup.rows : []; ++ if (!rows.length) { ++ this.renderStartupPending("Safe Local API startup diagnostics returned no rows."); ++ return; ++ } ++ const fragment = document.createDocumentFragment(); ++ rows.forEach((startupRow) => { ++ const row = document.createElement("tr"); ++ row.append( ++ this.createCell(startupRow.field), ++ this.createCell(startupRow.value), ++ this.createStatusCell(startupRow.status, startupRow.reason || localApiStartup.message), ++ ); ++ fragment.append(row); ++ }); ++ this.startupRows.replaceChildren(fragment); ++ } ++ + storageResultTarget(result = {}) { + if (typeof result.keysListed === "number" && result.actionId === "storage-list") { + return `${result.keysListed} object(s) under ${asText(result.projectsPrefix, "configured prefix")}`; +@@ -176,7 +197,7 @@ class AdminSystemHealthController { + + createStatusCell(status, reason) { + const cell = document.createElement("td"); +- cell.dataset.healthStatus = statusValue(status); ++ cell.dataset.healthStatus = normalizeStatusValue(status); + this.setStatusNode(cell, status, reason); + return cell; + } +@@ -230,6 +251,7 @@ class AdminSystemHealthController { + return; + } + this.renderPostgresStatus(data?.databaseStatus || {}); ++ this.renderStartupDiagnostics(data?.localApiStartup || {}); + this.renderStorageStatus(data?.storageStatus || {}); + this.runStorageDiagnostics(); + this.renderRuntimeEnvironment(data?.runtimeEnvironment || {}); +diff --git a/assets/theme-v2/js/gamefoundry-partials.js b/assets/theme-v2/js/gamefoundry-partials.js +index 44b301916..f7f220880 100644 +--- a/assets/theme-v2/js/gamefoundry-partials.js ++++ b/assets/theme-v2/js/gamefoundry-partials.js +@@ -1048,6 +1048,32 @@ + } + } + ++ async function renderToolboxStatusBar() { ++ const pagePath = currentPagePath() || ""; ++ if (pagePath.indexOf("toolbox/") !== 0) { ++ return; ++ } ++ try { ++ const module = await import(assetUrl("js/toolbox-status-bar.js")); ++ if (typeof module.mountToolboxStatusBar === "function") { ++ module.mountToolboxStatusBar({ ++ gameHubHref: routeHref("game-hub"), ++ pagePath ++ }); ++ } ++ } catch (error) { ++ console.warn("[toolbox/status] Shared status bar could not be loaded.", error); ++ } ++ } ++ ++ function refreshSharedSurfaces() { ++ renderPlatformBanner() ++ .then(renderToolboxStatusBar) ++ .catch(function (error) { ++ console.error(error); ++ }); ++ } ++ + enforcePageProtection(); + document.addEventListener("DOMContentLoaded", function () { + enforcePageProtection(); +@@ -1056,11 +1082,11 @@ + replaceExisting("header-nav", "header.site-header"), + replaceExisting("footer", "footer.footer") + ]; +- Promise.all(tasks).then(renderPlatformBanner).catch(function (error) { ++ Promise.all(tasks).then(refreshSharedSurfaces).catch(function (error) { + console.error(error); + }); + }); + window.addEventListener("gamefoundry:session-user-changed", refreshHeaderLoginState); + window.addEventListener("gamefoundry:data-changed", refreshHeaderOnly); +- window.addEventListener("gamefoundry:platform-settings-changed", renderPlatformBanner); ++ window.addEventListener("gamefoundry:platform-settings-changed", refreshSharedSurfaces); + }()); +diff --git a/assets/theme-v2/js/toolbox-status-bar.js b/assets/theme-v2/js/toolbox-status-bar.js +new file mode 100644 +index 000000000..fdb367328 +--- /dev/null ++++ b/assets/theme-v2/js/toolbox-status-bar.js +@@ -0,0 +1,348 @@ ++import { createServerRepositoryClient } from "/src/api/server-api-client.js"; ++ ++const EXCLUDED_SELECTED_GAME_TOOLS = new Set(["idea-board"]); ++const STATUS_BAR_SELECTOR = "[data-toolbox-status-bar]"; ++ ++let repository = null; ++let messageObserver = null; ++let listenersInstalled = false; ++let latestToolMessage = ""; ++let pendingToolMessageRefresh = 0; ++let mountOptions = { ++ gameHubHref: "toolbox/game-hub/index.html", ++ pagePath: "", ++}; ++ ++function getRepository() { ++ if (!repository) { ++ repository = createServerRepositoryClient("game-hub"); ++ } ++ return repository; ++} ++ ++function toolSlugFromPath(pagePath) { ++ const parts = String(pagePath || window.location.pathname || "") ++ .replace(/^\/+/, "") ++ .split("/") ++ .filter(Boolean); ++ if (parts[0] !== "toolbox") { ++ return ""; ++ } ++ const slug = (parts[1] || "index").replace(/\.html$/i, ""); ++ return slug || "index"; ++} ++ ++function pageRequiresSelectedGame() { ++ const slug = toolSlugFromPath(mountOptions.pagePath); ++ return !EXCLUDED_SELECTED_GAME_TOOLS.has(slug); ++} ++ ++function isRepositoryError(value) { ++ return Boolean(value && typeof value === "object" && value.error === true); ++} ++ ++function normalizeSelectedGame(value) { ++ if (!value) { ++ return null; ++ } ++ if (isRepositoryError(value)) { ++ throw new Error(value.message || "Game Hub selected game is unavailable."); ++ } ++ const id = String(value.id || "").trim(); ++ const name = String(value.name || "").trim(); ++ if (!id || !name || !Array.isArray(value.members)) { ++ throw new Error("Game Hub selected game payload is malformed."); ++ } ++ return Object.freeze({ ++ id, ++ name, ++ ownerKey: String(value.ownerKey || "").trim(), ++ purpose: String(value.purpose || "Game").trim() || "Game", ++ status: String(value.status || "").trim(), ++ }); ++} ++ ++function readSelectedGame() { ++ return normalizeSelectedGame(getRepository().getActiveGame()); ++} ++ ++function createText(tagName, className, datasetName) { ++ const element = document.createElement(tagName); ++ if (className) { ++ element.className = className; ++ } ++ if (datasetName) { ++ element.dataset[datasetName] = ""; ++ } ++ return element; ++} ++ ++function createStatusBar() { ++ const bar = document.createElement("section"); ++ bar.className = "toolbox-status-bar"; ++ bar.dataset.toolboxStatusBar = ""; ++ bar.setAttribute("aria-label", "Toolbox selected game status"); ++ ++ const inner = document.createElement("div"); ++ inner.className = "toolbox-status-bar__inner"; ++ ++ const game = document.createElement("div"); ++ game.className = "toolbox-status-bar__game"; ++ game.dataset.toolboxSelectedGame = ""; ++ ++ const nameField = document.createElement("div"); ++ nameField.className = "toolbox-status-bar__field"; ++ nameField.dataset.toolboxSelectedGameNameField = ""; ++ const nameLabel = createText("span", "toolbox-status-bar__label", "toolboxSelectedGameNameLabel"); ++ nameLabel.textContent = "Selected Game Name"; ++ const name = createText("strong", "toolbox-status-bar__game-name", "toolboxSelectedGameName"); ++ nameField.append(nameLabel, name); ++ ++ const purposeField = document.createElement("div"); ++ purposeField.className = "toolbox-status-bar__field"; ++ purposeField.dataset.toolboxSelectedGamePurposeField = ""; ++ const purposeLabel = createText("span", "toolbox-status-bar__label", "toolboxSelectedGamePurposeLabel"); ++ purposeLabel.textContent = "Selected Game Purpose"; ++ const purpose = createText("span", "toolbox-status-bar__purpose", "toolboxSelectedGamePurpose"); ++ purpose.dataset.toolboxSelectedGameMeta = ""; ++ purposeField.append(purposeLabel, purpose); ++ game.append(nameField, purposeField); ++ ++ const center = document.createElement("div"); ++ center.className = "toolbox-status-bar__center"; ++ center.dataset.toolboxStatusCenter = ""; ++ ++ const contextType = createText("span", "pill toolbox-status-bar__context-type", "toolboxStatusContextType"); ++ const message = createText("p", "toolbox-status-bar__message status", "toolboxStatusMessage"); ++ message.setAttribute("role", "status"); ++ const action = document.createElement("a"); ++ action.className = "btn btn--compact toolbox-status-bar__action"; ++ action.dataset.toolboxStatusAction = ""; ++ action.href = mountOptions.gameHubHref; ++ action.textContent = "Open Game Hub"; ++ center.append(contextType, message, action); ++ ++ inner.append(game, center); ++ bar.append(inner); ++ return bar; ++} ++ ++function ensureStatusBar() { ++ let bar = document.querySelector(STATUS_BAR_SELECTOR); ++ if (!bar) { ++ bar = createStatusBar(); ++ } ++ placeStatusBar(bar); ++ return bar; ++} ++ ++function placeStatusBar(bar) { ++ const footer = document.querySelector("footer.footer"); ++ if (footer?.parentNode) { ++ if (bar.nextElementSibling !== footer) { ++ footer.before(bar); ++ } ++ return; ++ } ++ ++ const main = document.querySelector("main"); ++ if (main?.parentNode) { ++ main.after(bar); ++ return; ++ } ++ ++ document.body.append(bar); ++} ++ ++function visibleStatusText(element) { ++ if (!element || element.closest(STATUS_BAR_SELECTOR) || element.hidden) { ++ return ""; ++ } ++ if (element.closest("[hidden]")) { ++ return ""; ++ } ++ return String(element.textContent || "").replace(/\s+/g, " ").trim(); ++} ++ ++function readToolMessage() { ++ const messages = Array.from(document.querySelectorAll("main [role='status'], main .status")) ++ .map((element, index) => ({ ++ index, ++ priority: Object.keys(element.dataset || {}).length > 0 ? 1 : 0, ++ text: visibleStatusText(element), ++ })) ++ .filter((entry) => entry.text); ++ const prioritized = messages ++ .filter((entry) => entry.priority > 0) ++ .pop(); ++ return prioritized?.text || messages[messages.length - 1]?.text || ""; ++} ++ ++function updateLatestToolMessage() { ++ const nextMessage = readToolMessage(); ++ if (nextMessage && nextMessage !== latestToolMessage) { ++ latestToolMessage = nextMessage; ++ refreshToolboxStatusBar(); ++ } ++} ++ ++function scheduleToolMessageRefresh() { ++ window.clearTimeout(pendingToolMessageRefresh); ++ pendingToolMessageRefresh = window.setTimeout(updateLatestToolMessage, 0); ++ window.setTimeout(updateLatestToolMessage, 120); ++} ++ ++function observeToolMessages() { ++ messageObserver?.disconnect(); ++ const main = document.querySelector("main"); ++ if (!main) { ++ return; ++ } ++ latestToolMessage = readToolMessage(); ++ messageObserver = new MutationObserver(updateLatestToolMessage); ++ messageObserver.observe(main, { ++ characterData: true, ++ childList: true, ++ subtree: true, ++ }); ++} ++ ++function publishSelectedGameContext(selectedGame, state) { ++ const required = pageRequiresSelectedGame(); ++ const context = Object.freeze({ ++ required, ++ selectedGame, ++ source: "game-hub", ++ state, ++ }); ++ window.GameFoundryToolboxSelectedGame = context; ++ document.body.dataset.toolboxSelectedGameFilter = required ? state : "optional"; ++ document.body.dataset.toolboxSelectedGameSource = "game-hub"; ++ if (selectedGame) { ++ document.body.dataset.toolboxSelectedGameId = selectedGame.id; ++ } else { ++ delete document.body.dataset.toolboxSelectedGameId; ++ } ++ document.body.classList.toggle("toolbox-selected-game-missing", required && state === "missing"); ++ document.body.classList.toggle("toolbox-selected-game-unavailable", required && state === "error"); ++ document.body.classList.toggle("toolbox-selected-game-optional", !required); ++ window.dispatchEvent(new CustomEvent("gamefoundry:toolbox-selected-game-context", { ++ detail: context, ++ })); ++} ++ ++function classifyToolContext(messageText, state, required) { ++ const text = String(messageText || "").trim(); ++ if (state === "error") { ++ return { kind: "error", label: "Error" }; ++ } ++ if (required && state === "missing") { ++ return { kind: "action", label: "Tool Action" }; ++ } ++ if (/\b(error|failed|malformed|unavailable|could not)\b/i.test(text)) { ++ return { kind: "error", label: "Error" }; ++ } ++ if (/\b(sign in|refresh|try again|temporarily|blocked)\b/i.test(text)) { ++ return { kind: "warning", label: "Warning" }; ++ } ++ if (/\b(validation|requirement|requirements|missing|required|open or seed)\b/i.test(text)) { ++ return { kind: "validation", label: "Validation" }; ++ } ++ if (/\b(saved|created|deleted|updated|loaded|save changes)\b/i.test(text)) { ++ return { kind: "save", label: "Save State" }; ++ } ++ return { kind: "action", label: "Tool Action" }; ++} ++ ++function renderSelectedGame(bar, selectedGame, state, messageText) { ++ const required = pageRequiresSelectedGame(); ++ const name = bar.querySelector("[data-toolbox-selected-game-name]"); ++ const purpose = bar.querySelector("[data-toolbox-selected-game-purpose]"); ++ const contextType = bar.querySelector("[data-toolbox-status-context-type]"); ++ const message = bar.querySelector("[data-toolbox-status-message]"); ++ const action = bar.querySelector("[data-toolbox-status-action]"); ++ const nextMessage = messageText || latestToolMessage || (selectedGame ++ ? `Tool context is filtered to ${selectedGame.name}.` ++ : required ++ ? "Select or create a game in Game Hub before using this toolbox page." ++ : "Idea Board can capture ideas before a Game Hub game exists."); ++ const context = classifyToolContext(nextMessage, state, required); ++ ++ bar.dataset.selectedGameState = state; ++ bar.dataset.selectedGameRequired = String(required); ++ bar.dataset.toolboxStatusContextKind = context.kind; ++ contextType.textContent = context.label; ++ action.hidden = false; ++ action.href = mountOptions.gameHubHref; ++ ++ if (selectedGame) { ++ name.textContent = selectedGame.name; ++ purpose.textContent = selectedGame.purpose || "Game"; ++ message.textContent = nextMessage; ++ action.textContent = "Open Game Hub"; ++ return; ++ } ++ ++ if (!required) { ++ name.textContent = "No game selected"; ++ purpose.textContent = "Idea Board optional"; ++ message.textContent = nextMessage; ++ action.textContent = "Open Game Hub"; ++ return; ++ } ++ ++ if (state === "error") { ++ name.textContent = "Unavailable"; ++ purpose.textContent = "Game Hub selected game could not be read"; ++ message.textContent = nextMessage; ++ action.textContent = "Open Game Hub"; ++ return; ++ } ++ ++ name.textContent = "No game selected"; ++ purpose.textContent = "Game Hub owns game selection"; ++ message.textContent = "Select or create a game in Game Hub before using this toolbox page."; ++ action.textContent = "Select or Create in Game Hub"; ++} ++ ++export function refreshToolboxStatusBar() { ++ const bar = ensureStatusBar(); ++ let selectedGame = null; ++ let state = "missing"; ++ let message = ""; ++ ++ try { ++ selectedGame = readSelectedGame(); ++ state = selectedGame ? "active" : "missing"; ++ } catch (error) { ++ state = "error"; ++ message = error instanceof Error ? error.message : String(error || "Game Hub selected game is unavailable."); ++ } ++ ++ publishSelectedGameContext(selectedGame, state); ++ renderSelectedGame(bar, selectedGame, state, message); ++ placeStatusBar(bar); ++} ++ ++function installEventListeners() { ++ if (listenersInstalled) { ++ return; ++ } ++ listenersInstalled = true; ++ document.addEventListener("click", scheduleToolMessageRefresh, true); ++ document.addEventListener("submit", scheduleToolMessageRefresh, true); ++ document.addEventListener("change", scheduleToolMessageRefresh, true); ++ window.addEventListener("gamefoundry:toolbox-selected-game-changed", refreshToolboxStatusBar); ++ window.addEventListener("gamefoundry:data-changed", refreshToolboxStatusBar); ++} ++ ++export function mountToolboxStatusBar(options = {}) { ++ mountOptions = { ++ ...mountOptions, ++ ...options, ++ }; ++ ensureStatusBar(); ++ observeToolMessages(); ++ installEventListeners(); ++ refreshToolboxStatusBar(); ++} +diff --git a/toolbox/assets/assets-upload-worker.js b/assets/toolbox/assets/js/assets-upload-worker.js +similarity index 100% +rename from toolbox/assets/assets-upload-worker.js +rename to assets/toolbox/assets/js/assets-upload-worker.js +diff --git a/toolbox/assets/assets.js b/assets/toolbox/assets/js/index.js +similarity index 99% +rename from toolbox/assets/assets.js +rename to assets/toolbox/assets/js/index.js +index 712917f48..d09e88a81 100644 +--- a/toolbox/assets/assets.js ++++ b/assets/toolbox/assets/js/index.js +@@ -2,8 +2,8 @@ import { + ASSET_CATALOG_TYPES, + ASSET_USAGE_OPTIONS, + createAssetToolApiRepository +-} from "./assets-api-client.js"; +-import { getSessionCurrent } from "../../src/api/session-api-client.js"; ++} from "../../../js/shared/assets-api-client.js"; ++import { getSessionCurrent } from "../../../../src/api/session-api-client.js"; + + const repository = createAssetToolApiRepository(); + const params = new URLSearchParams(window.location.search); +diff --git a/toolbox/colors/colors.js b/assets/toolbox/colors/js/index.js +similarity index 97% +rename from toolbox/colors/colors.js +rename to assets/toolbox/colors/js/index.js +index 99efed4f5..dcc0631e5 100644 +--- a/toolbox/colors/colors.js ++++ b/assets/toolbox/colors/js/index.js +@@ -1,20 +1,36 @@ + import { +- CURATED_PALETTE_COLLECTIONS, +- NUMERIC_VARIANT_COUNTS, +- PALETTE_SOURCE_USER, +- PALETTE_GENERATOR_DEFAULTS, +- PALETTE_TOOL_KEY, +- PALETTE_VARIANTS, +- PALETTE_WORKSPACE_PATH, +- PICKER_PREVIEW_DEFAULTS, +- PICKER_PREVIEW_SORT_OPTIONS, +- SIZE_OPTIONS, +- SORT_OPTIONS, +- SUGGESTED_TAGS, +- createGameWorkspacePaletteApiRepository, +- normalizePaletteSwatchInput, +- validatePaletteSwatchInput +-} from "./palette-api-client.js"; ++ callServerToolFunction, ++ createServerRepositoryClient, ++ readServerToolConstants, ++ requireServerConstant, ++} from "../../../../src/api/server-api-client.js"; ++ ++const constants = readServerToolConstants("palette"); ++ ++export const PALETTE_SOURCE_USER = requireServerConstant(constants, "PALETTE_SOURCE_USER", "palette"); ++export const PALETTE_TOOL_KEY = requireServerConstant(constants, "PALETTE_TOOL_KEY", "palette"); ++export const PALETTE_WORKSPACE_PATH = requireServerConstant(constants, "PALETTE_WORKSPACE_PATH", "palette"); ++export const CURATED_PALETTE_COLLECTIONS = Object.freeze(requireServerConstant(constants, "CURATED_PALETTE_COLLECTIONS", "palette")); ++export const NUMERIC_VARIANT_COUNTS = Object.freeze(requireServerConstant(constants, "NUMERIC_VARIANT_COUNTS", "palette")); ++export const PALETTE_GENERATOR_DEFAULTS = Object.freeze(requireServerConstant(constants, "PALETTE_GENERATOR_DEFAULTS", "palette")); ++export const PALETTE_VARIANTS = Object.freeze(requireServerConstant(constants, "PALETTE_VARIANTS", "palette")); ++export const PICKER_PREVIEW_DEFAULTS = Object.freeze(requireServerConstant(constants, "PICKER_PREVIEW_DEFAULTS", "palette")); ++export const PICKER_PREVIEW_SORT_OPTIONS = Object.freeze(requireServerConstant(constants, "PICKER_PREVIEW_SORT_OPTIONS", "palette")); ++export const SIZE_OPTIONS = Object.freeze(requireServerConstant(constants, "SIZE_OPTIONS", "palette")); ++export const SORT_OPTIONS = Object.freeze(requireServerConstant(constants, "SORT_OPTIONS", "palette")); ++export const SUGGESTED_TAGS = Object.freeze(requireServerConstant(constants, "SUGGESTED_TAGS", "palette")); ++ ++export function createGameWorkspacePaletteApiRepository(options = {}) { ++ return createServerRepositoryClient("palette", options); ++} ++ ++export function normalizePaletteSwatchInput(input) { ++ return callServerToolFunction("palette", "normalizePaletteSwatchInput", input); ++} ++ ++export function validatePaletteSwatchInput(input, existingSwatches, options) { ++ return callServerToolFunction("palette", "validatePaletteSwatchInput", input, existingSwatches, options); ++} + + const params = new URLSearchParams(window.location.search); + +diff --git a/toolbox/controls/controls.js b/assets/toolbox/controls/js/index.js +similarity index 99% +rename from toolbox/controls/controls.js +rename to assets/toolbox/controls/js/index.js +index c3355a91e..6a648b878 100644 +--- a/toolbox/controls/controls.js ++++ b/assets/toolbox/controls/js/index.js +@@ -5,11 +5,11 @@ import { + GAME_CONTROL_NORMALIZED_INPUTS, + NORMALIZED_USAGE_LABELS, + createControlsToolApiRepository, +-} from "./controls-api-client.js"; ++} from "../../../js/shared/controls-api-client.js"; + import { + normalizeNormalizedInput, + normalizedInputOptions, +-} from "../../src/engine/input/NormalizedInputRegistry.js"; ++} from "../../../../src/engine/input/NormalizedInputRegistry.js"; + + const GAME_CONTROL_NORMALIZED_INPUT_IDS = new Set(GAME_CONTROL_NORMALIZED_INPUTS); + +diff --git a/toolbox/events/events.js b/assets/toolbox/events/js/index.js +similarity index 99% +rename from toolbox/events/events.js +rename to assets/toolbox/events/js/index.js +index 80df22928..837198395 100644 +--- a/toolbox/events/events.js ++++ b/assets/toolbox/events/js/index.js +@@ -1,7 +1,7 @@ + import { + requireServerApiData, + safeRequestServerApi, +-} from "../../src/api/server-api-client.js"; ++} from "../../../../src/api/server-api-client.js"; + + const NEW_ROW_KEY = "__new__"; + const TABLE_COLSPAN = 5; +diff --git a/toolbox/game-configuration/game-configuration.js b/assets/toolbox/game-configuration/js/index.js +similarity index 92% +rename from toolbox/game-configuration/game-configuration.js +rename to assets/toolbox/game-configuration/js/index.js +index b907ca893..d94f3ced2 100644 +--- a/toolbox/game-configuration/game-configuration.js ++++ b/assets/toolbox/game-configuration/js/index.js +@@ -1,7 +1,17 @@ + import { +- GAME_CONFIGURATION_SECTIONS, +- createGameConfigurationApiRepository +-} from "./game-configuration-api-client.js"; ++ createServerRepositoryClient, ++ readServerToolConstants, ++ requireServerConstant, ++} from "../../../../src/api/server-api-client.js"; ++ ++const constants = readServerToolConstants("game-configuration"); ++ ++export const GAME_CONFIGURATION_SECTIONS = Object.freeze(requireServerConstant(constants, "GAME_CONFIGURATION_SECTIONS", "game-configuration")); ++export const GAME_CONFIGURATION_PLAYER_MODES = Object.freeze(requireServerConstant(constants, "GAME_CONFIGURATION_PLAYER_MODES", "game-configuration")); ++ ++export function createGameConfigurationApiRepository(options = {}) { ++ return createServerRepositoryClient("game-configuration", options); ++} + + const repository = createGameConfigurationApiRepository(); + const params = new URLSearchParams(window.location.search); +diff --git a/toolbox/game-design/game-design.js b/assets/toolbox/game-design/js/index.js +similarity index 92% +rename from toolbox/game-design/game-design.js +rename to assets/toolbox/game-design/js/index.js +index 51466ecff..af61960cd 100644 +--- a/toolbox/game-design/game-design.js ++++ b/assets/toolbox/game-design/js/index.js +@@ -1,10 +1,19 @@ + import { +- GAME_DESIGN_GAME_TYPES, +- GAME_DESIGN_GENRES, +- GAME_DESIGN_PLAYER_MODES, +- GAME_DESIGN_PLAY_STYLES, +- createGameDesignApiRepository +-} from "./game-design-api-client.js"; ++ createServerRepositoryClient, ++ readServerToolConstants, ++ requireServerConstant, ++} from "../../../../src/api/server-api-client.js"; ++ ++const constants = readServerToolConstants("game-design"); ++ ++export const GAME_DESIGN_GAME_TYPES = Object.freeze(requireServerConstant(constants, "GAME_DESIGN_GAME_TYPES", "game-design")); ++export const GAME_DESIGN_GENRES = Object.freeze(requireServerConstant(constants, "GAME_DESIGN_GENRES", "game-design")); ++export const GAME_DESIGN_PLAYER_MODES = Object.freeze(requireServerConstant(constants, "GAME_DESIGN_PLAYER_MODES", "game-design")); ++export const GAME_DESIGN_PLAY_STYLES = Object.freeze(requireServerConstant(constants, "GAME_DESIGN_PLAY_STYLES", "game-design")); ++ ++export function createGameDesignApiRepository(options = {}) { ++ return createServerRepositoryClient("game-design", options); ++} + + const repository = createGameDesignApiRepository(); + const params = new URLSearchParams(window.location.search); +diff --git a/toolbox/game-journey/game-journey.js b/assets/toolbox/game-journey/js/index.js +similarity index 98% +rename from toolbox/game-journey/game-journey.js +rename to assets/toolbox/game-journey/js/index.js +index c2b2f876c..085df839c 100644 +--- a/toolbox/game-journey/game-journey.js ++++ b/assets/toolbox/game-journey/js/index.js +@@ -6,11 +6,11 @@ import { + GAME_JOURNEY_SUGGESTED_TOOLS, + createGameJourneyApiRepository, + readGameJourneyCompletionMetrics, +-} from "./game-journey-api-client.js"; ++} from "../../../js/shared/game-journey-api-client.js"; + import { + getActiveToolRegistry, + getToolRegistryApiDiagnostic, +-} from "../tool-registry-api-client.js"; ++} from "../../../../toolbox/tool-registry-api-client.js"; + + const repository = createGameJourneyApiRepository(); + const registryDiagnostic = getToolRegistryApiDiagnostic(); +@@ -1161,7 +1161,7 @@ function renderRecommendedTargets() { + table.setAttribute("aria-label", "Game Journey recommended planning targets"); + const head = createElement("thead"); + const headRow = createElement("tr"); +- ["Target", "Section", "Suggested"].forEach((heading) => { ++ ["Target", "Section", "Count"].forEach((heading) => { + const cell = createElement("th", { text: heading }); + cell.scope = "col"; + headRow.append(cell); +@@ -1169,16 +1169,22 @@ function renderRecommendedTargets() { + head.append(headRow); + const body = createElement("tbody"); + targets.forEach((target) => { ++ const targetCount = recommendedTargetValues.get(target.key) ?? target.suggestedCount; + const row = createElement("tr"); + row.dataset.journeyRecommendedTarget = target.key; +- const labelCell = createElement("td", { text: target.label }); ++ const labelCell = createElement("td"); ++ const label = createElement("span", { text: target.label }); ++ const countPreview = createElement("span", { text: ` [${targetCount}]` }); ++ countPreview.dataset.journeyTargetCountPreview = target.key; ++ labelCell.append(label, countPreview); + const sectionCell = createElement("td", { text: target.sectionName }); + const input = document.createElement("input"); + input.type = "number"; ++ input.inputMode = "numeric"; + input.min = "0"; + input.step = "1"; +- input.value = String(recommendedTargetValues.get(target.key) ?? target.suggestedCount); +- input.setAttribute("aria-label", `${target.label} suggested target`); ++ input.value = String(targetCount); ++ input.setAttribute("aria-label", `${target.label} count`); + input.dataset.journeyTargetInput = target.key; + const inputCell = createElement("td"); + inputCell.append(input); +@@ -1773,6 +1779,10 @@ recommendedTargets?.addEventListener("input", (event) => { + const savedValue = normalizeTargetCount(updated.suggestedCount); + recommendedTargetValues.set(target.key, savedValue); + input.value = String(savedValue); ++ const preview = recommendedTargets.querySelector(`[data-journey-target-count-preview='${target.key}']`); ++ if (preview) { ++ preview.textContent = ` [${savedValue}]`; ++ } + if (recommendedTargetStatus) { + recommendedTargetStatus.textContent = `Saved ${target.label} target at ${savedValue}.`; + } +diff --git a/toolbox/idea-board/index.js b/assets/toolbox/idea-board/js/index.js +similarity index 93% +rename from toolbox/idea-board/index.js +rename to assets/toolbox/idea-board/js/index.js +index d1e339336..47a5ed91e 100644 +--- a/toolbox/idea-board/index.js ++++ b/assets/toolbox/idea-board/js/index.js +@@ -1,9 +1,12 @@ +-import { createServerRepositoryClient } from "../../src/api/server-api-client.js"; ++import { createServerRepositoryClient } from "../../../../src/api/server-api-client.js"; ++import { getSessionCurrent } from "../../../../src/api/session-api-client.js"; + +-const statusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); ++const editableStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready"]); ++const filterStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); + const defaultVisibleStatuses = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project"]); + const userId = "user-1"; + const gameHubRoute = "toolbox/game-hub/index.html"; ++const signInRoute = "account/sign-in.html"; + let gameHubRepository = null; + + const ideaTable = [ +@@ -121,7 +124,7 @@ function visibleIdeas() { + } + + function previousStatusForRestore(record) { +- return statusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" ++ return filterStatusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" + ? record.previousStatus + : "Refining"; + } +@@ -166,7 +169,7 @@ function renderStatusFilter(root) { + const options = root.querySelector("[data-idea-board-status-options]"); + if (!options) return; + options.replaceChildren(); +- for (const status of statusOptions) { ++ for (const status of filterStatusOptions) { + const label = document.createElement("label"); + label.className = "idea-board-show-filter__option"; + const input = document.createElement("input"); +@@ -193,7 +196,7 @@ function statusSelect(value) { + const select = document.createElement("select"); + select.setAttribute("aria-label", "Idea status"); + select.dataset.ideaBoardIdeaStatusInput = "true"; +- for (const optionValue of statusOptions) { ++ for (const optionValue of editableStatusOptions) { + const option = document.createElement("option"); + option.value = optionValue; + option.textContent = optionValue; +@@ -229,7 +232,6 @@ function renderIdeaInputRow(tbody, record = null) { + const statusCell = document.createElement("td"); + statusCell.append(statusSelect(record?.status || "New")); + row.append(statusCell); +- row.append(cell(record?.updated || today())); + row.append(cell(record ? noteCountLabel(record.ideaId) : "0 Notes")); + + const actions = document.createElement("td"); +@@ -272,7 +274,6 @@ function renderIdeaRow(tbody, record) { + row.append(idea); + row.append(cell(record.pitch)); + row.append(cell(record.status)); +- row.append(cell(record.updated)); + + const notes = document.createElement("td"); + const notesCount = document.createElement("span"); +@@ -351,7 +352,7 @@ function renderExpandedNotesRow(tbody, record) { + row.dataset.ideaBoardExpandedRow = record.ideaId; + + const content = document.createElement("td"); +- content.colSpan = 6; ++ content.colSpan = 5; + + const childSurface = document.createElement("div"); + childSurface.className = "idea-board-notes-child-surface"; +@@ -398,7 +399,7 @@ function renderAddIdeaRow(tbody) { + const row = document.createElement("tr"); + row.dataset.ideaBoardAddIdeaRow = "true"; + const actions = document.createElement("td"); +- actions.colSpan = 6; ++ actions.colSpan = 5; + const addIdea = actionButton("Add Idea", "add", "ideaBoardIdeaAction", "primary"); + addIdea.dataset.ideaBoardAddIdea = "true"; + actions.append(addIdea); +@@ -561,6 +562,28 @@ function gameHubUrl(record) { + return `${gameHubRoute}${suffix}`; + } + ++function signInUrl() { ++ return new URL(signInRoute, document.baseURI || window.location.href).href; ++} ++ ++function currentSessionState() { ++ try { ++ const session = getSessionCurrent(); ++ return { ++ apiAvailable: true, ++ authenticated: Boolean(session?.authenticated && session.userKey), ++ session, ++ }; ++ } catch (error) { ++ console.warn("Idea Board could not verify the current session.", error instanceof Error ? error.message : String(error || "")); ++ return { ++ apiAvailable: false, ++ authenticated: false, ++ session: null, ++ }; ++ } ++} ++ + function createProject(root, ideaId) { + const record = ideaRecord(ideaId); + if (!record) { +@@ -571,6 +594,16 @@ function createProject(root, ideaId) { + updateStatus(root, "Set this idea to Ready before creating a project."); + return; + } ++ const sessionState = currentSessionState(); ++ if (!sessionState.apiAvailable) { ++ updateStatus(root, "Sign-in status could not be verified. Try again shortly."); ++ return; ++ } ++ if (!sessionState.authenticated) { ++ updateStatus(root, "Sign in to create a Game Hub project."); ++ window.location.href = signInUrl(); ++ return; ++ } + const repository = gameHubProjectRepository(); + const project = repository.createGame({ + name: record.idea, +@@ -723,7 +756,7 @@ function handleNoteAction(root, actionControl) { + + function handleFilterAction(root, actionControl) { + if (actionControl.matches("[data-idea-board-filter-select-all]")) { +- state.visibleStatuses = new Set(statusOptions); ++ state.visibleStatuses = new Set(filterStatusOptions); + updateStatus(root, "Showing all statuses."); + } else if (actionControl.matches("[data-idea-board-filter-clear-all]")) { + state.visibleStatuses = new Set(); +diff --git a/toolbox/objects/objects.js b/assets/toolbox/objects/js/index.js +similarity index 96% +rename from toolbox/objects/objects.js +rename to assets/toolbox/objects/js/index.js +index bd600c533..0f332df58 100644 +--- a/toolbox/objects/objects.js ++++ b/assets/toolbox/objects/js/index.js +@@ -1,16 +1,49 @@ ++import { ++ createServerRepositoryClient, ++ readServerToolConstants, ++ requireServerConstant, ++} from "../../../../src/api/server-api-client.js"; + import { + OBJECT_MODEL_TRAIT_LIST, + getObjectModelTrait, + getObjectModelType, + validateObjectDefinition, +-} from "../../src/engine/object-model/index.js"; +-import { createAssetToolApiRepository } from "../assets/assets-api-client.js"; +-import { +- CAPABILITY_LABELS, +- OBJECT_TYPE_TEMPLATES, +- STARTER_OBJECTS, +- createObjectsToolApiRepository, +-} from "./objects-api-client.js"; ++} from "../../../../src/engine/object-model/index.js"; ++import { createAssetToolApiRepository } from "../../../js/shared/assets-api-client.js"; ++ ++const constants = readServerToolConstants("objects"); ++ ++function freezeTemplate(template = {}) { ++ return Object.freeze({ ++ ...template, ++ capabilities: Object.freeze(Array.isArray(template.capabilities) ? [...template.capabilities] : []), ++ }); ++} ++ ++function freezeStarterObject(object = {}) { ++ return Object.freeze({ ++ ...object, ++ render: Object.freeze({ ...(object.render || {}) }), ++ }); ++} ++ ++export const CAPABILITY_LABELS = Object.freeze( ++ { ...requireServerConstant(constants, "CAPABILITY_LABELS", "objects") }, ++); ++ ++export const OBJECT_TYPE_TEMPLATES = Object.freeze( ++ requireServerConstant(constants, "OBJECT_TYPE_TEMPLATES", "objects").map(freezeTemplate), ++); ++ ++export const OBJECTS_TOOL_TABLES = Object.freeze(requireServerConstant(constants, "OBJECTS_TOOL_TABLES", "objects")); ++ ++export const STARTER_OBJECTS = Object.freeze( ++ requireServerConstant(constants, "STARTER_OBJECTS", "objects").map(freezeStarterObject), ++); ++ ++export function createObjectsToolApiRepository(options = {}) { ++ return createServerRepositoryClient("objects", options); ++} + + let assetRepository = createAssetToolApiRepository(); + let objectsRepository = createObjectsToolApiRepository(); +diff --git a/toolbox/tags/tags.js b/assets/toolbox/tags/js/index.js +similarity index 95% +rename from toolbox/tags/tags.js +rename to assets/toolbox/tags/js/index.js +index c43c6fc89..118923da9 100644 +--- a/toolbox/tags/tags.js ++++ b/assets/toolbox/tags/js/index.js +@@ -1,4 +1,16 @@ +-import { createTagsToolApiRepository } from "./tags-api-client.js"; ++import { ++ createServerRepositoryClient, ++ readServerToolConstants, ++ requireServerConstant, ++} from "../../../../src/api/server-api-client.js"; ++ ++const constants = readServerToolConstants("tags"); ++ ++export const TAGS_TOOL_TABLES = Object.freeze(requireServerConstant(constants, "TAGS_TOOL_TABLES", "tags")); ++ ++export function createTagsToolApiRepository(options = {}) { ++ return createServerRepositoryClient("tags", options); ++} + + const repository = createTagsToolApiRepository(); + +diff --git a/toolbox/text-to-speech/text2speech.js b/assets/toolbox/text-to-speech/js/index.js +similarity index 99% +rename from toolbox/text-to-speech/text2speech.js +rename to assets/toolbox/text-to-speech/js/index.js +index 081fa25b2..77e090f4e 100644 +--- a/toolbox/text-to-speech/text2speech.js ++++ b/assets/toolbox/text-to-speech/js/index.js +@@ -1,7 +1,7 @@ + import { + textToSpeechLanguageOptionsFromVoices, + TextToSpeechEngine, +-} from "../../src/engine/audio/TextToSpeechEngine.js"; ++} from "../../../../src/engine/audio/TextToSpeechEngine.js"; + import { + TEXT_TO_SPEECH_AGE_FILTER_OPTIONS, + TEXT_TO_SPEECH_DEFAULTS, +@@ -9,11 +9,11 @@ import { + TEXT_TO_SPEECH_LANGUAGE_OPTIONS, + TEXT_TO_SPEECH_RANGE_DEFAULTS, + TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS +-} from "../../src/engine/audio/TextToSpeechDefaults.js"; ++} from "../../../../src/engine/audio/TextToSpeechDefaults.js"; + import { + readSavedTextToSpeechProfiles, + writeSavedTextToSpeechProfiles, +-} from "./tts-profile-store.js"; ++} from "../../../js/shared/tts-profile-store.js"; + + const TTS_OWNERSHIP = Object.freeze({ + DESIGN: "Design", +diff --git a/docs_build/dev/BUILD_PR.md b/docs_build/dev/BUILD_PR.md +index 20d0cb9f0..dd5b4d28f 100644 +--- a/docs_build/dev/BUILD_PR.md ++++ b/docs_build/dev/BUILD_PR.md +@@ -1,27 +1,63 @@ +-# BUILD_PR: Schema location correction ++# PR_26175_ALFA_008-game-hub-feature-matrix + +-## Codex task +-Move the schema contract plan from root-level schema files to `src/shared/schemas/`. ++## Purpose ++Audit the current Game Hub workflow and publish a feature matrix that maps implemented creator-facing behavior to code and Playwright evidence. + +-## Required changes +-1. Remove any planned or newly-added root-level `*.schema.json` files. +-2. Add schema contracts under `src/shared/schemas/` only. +-3. Put reusable manifest schemas directly under `src/shared/schemas/`. +-4. Put individual tool payload schemas under `src/shared/schemas/tools/`. +-5. Update any docs or references to point to the new schema paths. ++## Source Of Truth ++This `BUILD_PR.md` is the source of truth for `PR_26175_ALFA_008-game-hub-feature-matrix`. + +-## Do not +-- Do not write broad validation utilities as the primary deliverable. +-- Do not change sample payloads. +-- Do not unlock or mutate samples. +-- Do not create schema files at repository root. +-- Do not modify start_of_day folders. ++## Exact Scope ++- Produce a Game Hub feature matrix only. ++- Audit Game Hub table workflow, selected/open game behavior, create/edit/delete actions, child tables, guest save gating, empty/error states, Theme V2 layout, and targeted Game Hub coverage. ++- Use current `main` behavior as evidence. ++- Preserve Game Hub UI/product behavior. ++- Preserve API/service/repository contracts. ++- Preserve previous ALFA Game Hub cleanup and create-validation behavior. ++- Do not implement product/UI changes unless validation exposes a requirement-critical defect. + +-## Validation command +-Search for misplaced schemas: ++## Exact Targets ++- `docs_build/dev/BUILD_PR.md` ++- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md` ++- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md` ++- `docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md` ++- `docs_build/dev/reports/codex_review.diff` ++- `docs_build/dev/reports/codex_changed_files.txt` ++ ++## Evidence Sources ++- `toolbox/game-hub/index.html` ++- `toolbox/project-workspace/index.html` ++- `toolbox/game-hub/game-hub.js` ++- `toolbox/game-hub/game-hub-api-client.js` ++- `src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js` ++- `tests/playwright/tools/GameHubMockRepository.spec.mjs` ++ ++## Out Of Scope ++- No Game Hub product or UI changes. ++- No Game Journey changes. ++- No shared toolbox status bar changes. ++- No browser-owned product data as source of truth. ++- No API/service/repository contract changes. ++- No inline styles, style blocks, or page-local CSS. ++- No engine core changes. ++- No `start_of_day` folder changes. ++- No ALFA_007 work. ++ ++## Validation ++Run targeted Game Hub validation: + + ```powershell +-Get-ChildItem -Path . -Filter *.schema.json -Recurse | Select-Object FullName ++npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --workers=1 + ``` + +-Expected result: all schema files are under `src\shared\schemas\`. ++Also verify changed docs/reports do not introduce inline styles or style blocks: ++ ++```powershell ++rg -n "<[s]tyle|[s]tyle=" docs_build/dev/BUILD_PR.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_report.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_validation-lane.md docs_build/dev/reports/PR_26175_ALFA_008-game-hub-feature-matrix_requirements-checklist.md ++``` ++ ++## Artifact ++Create repo-structured delta ZIP: ++ ++```text ++tmp/PR_26175_ALFA_008-game-hub-feature-matrix_delta.zip ++``` +diff --git a/docs_build/dev/ProjectInstructions/PROJECT_INSTRUCTIONS.md b/docs_build/dev/ProjectInstructions/PROJECT_INSTRUCTIONS.md +index 40db33c44..3314b3dea 100644 +--- a/docs_build/dev/ProjectInstructions/PROJECT_INSTRUCTIONS.md ++++ b/docs_build/dev/ProjectInstructions/PROJECT_INSTRUCTIONS.md +@@ -44,3 +44,16 @@ OWNER follows the same safety rules: + - One active OWNER assignment at a time. + - OWNER may override team locks, but may not silently delete, rewrite, or remove protected instructions. + - OWNER override must be explicitly documented. ++ ++## Four-Team Ownership Alignment ++ ++The single authoritative four-team ownership definition is: ++ ++`docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md` ++ ++Use the `Current Four-Team Ownership Model` section there for team ownership, assignment routing, and cross-team scope checks. ++ ++Rules: ++- Teams pull backlog items only from their ownership area unless OWNER explicitly reassigns or splits the work. ++- Cross-team work requires OWNER approval and must identify the owning team for each PR. ++- Team start commands must remain aligned with this ownership model. +diff --git a/docs_build/dev/ProjectInstructions/TEAM_START_COMMANDS.md b/docs_build/dev/ProjectInstructions/TEAM_START_COMMANDS.md +index 35279e8f1..12f74cb1f 100644 +--- a/docs_build/dev/ProjectInstructions/TEAM_START_COMMANDS.md ++++ b/docs_build/dev/ProjectInstructions/TEAM_START_COMMANDS.md +@@ -66,6 +66,30 @@ Create one Team Charlie branch for the selected assignment. + Work only that assignment. + ``` + ++## Start Team Delta ++ ++Ready-to-copy command: ++ ++```text ++OWNER override approved: Start Team Delta from the ProjectInstructions release gate. ++ ++Read docs_build/dev/ProjectInstructions/README.txt first. ++Read docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md. ++Read docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md. ++Read docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md. ++ ++Pull one [ ] item for Team Delta from BACKLOG_MASTER.md. ++Stop if Team Delta already has an active branch. ++Stop if Team Delta already has an active assignment. ++Stop if no [ ] Team Delta backlog item is available. ++Stop if the selected item is outside Team Delta ownership. ++ ++Change the selected backlog item from [ ] to [.]. ++Add the selected assignment under Team Delta in TEAM_ASSIGNMENTS.md. ++Create one Team Delta branch for the selected assignment. ++Work only that assignment. ++``` ++ + ## Day Work / EOD Merge Reminder + + Ready-to-copy reminder: +diff --git a/docs_build/dev/ProjectInstructions/addendums/multi_team.md b/docs_build/dev/ProjectInstructions/addendums/multi_team.md +index 4b45257b0..3d603aff9 100644 +--- a/docs_build/dev/ProjectInstructions/addendums/multi_team.md ++++ b/docs_build/dev/ProjectInstructions/addendums/multi_team.md +@@ -1,5 +1,18 @@ + # Multi-Team Codex Execution Governance + ++## Four Active Delivery Teams ++ ++The single authoritative four-team ownership definition is: ++ ++`docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md` ++ ++Use the `Current Four-Team Ownership Model` section there for active delivery team ownership. ++ ++Rules: ++- Team work must stay inside the owning team's area. ++- Cross-team work requires OWNER approval and must identify the correct owning team for each PR. ++- Team start commands must use the current ownership model before pulling a backlog item. ++ + ## All-Team Preferred Codex Execution Method + + Preferred execution model: +diff --git a/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md b/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md +index 23ceb1622..5645ebcbb 100644 +--- a/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md ++++ b/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md +@@ -15,3 +15,69 @@ Avoid: + + Reference implementation: + Idea Board is the reference implementation. ++ ++ ++DB base ++Creator Table 1 ++Parent Table *-1 user ++Child Table -- *-1 Parent ++ ++ ++No selected Items ++┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐ ++│ Idea Board │ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ ++├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ ++│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Sky Orchard │ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ [ Add Idea ] │ ++└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ ++ ++ ++Clicking {Sky Orchard {chevron down arrow}] Expands/Acording the Note(s) for that Idea ++┌────────────────────────────────────────────────────────────────────────────────────────────────────┐ ++│ Idea Board │ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ ++├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ ++│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Sky Orchard[^}│ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ Notes │ ++│ --------------------------------------------------------------------------------------------------- │ ++│ note 1 [Edit] [Delete] │ ++│ System seed note: compare early ideas before project creation. [Edit] │ ++│ Ask whether the core loop is planning, defense, or both. [Edit] [Delete] │ ++│ │ ++│ [ Add Note ] │ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ [ Add Idea ] │ ++└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ ++ ++Clickin Add Idea ++┌────────────────────────────────────────────────────────────────────────────────────────────────────┐ ++│ Idea Board │ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ ++├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ ++│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ ++│ Sky Orchard │ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ ++├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ ++│ [input.....] │ [input.....] │ [Dropdown]│ [autofile] │ 0 Notes │ Save Can│ ++└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ ++ ++ ++ ++ ++ +diff --git a/docs_build/dev/ProjectInstructions/addendums/team_start_and_release.md b/docs_build/dev/ProjectInstructions/addendums/team_start_and_release.md +index 3f85bbf25..e1018c155 100644 +--- a/docs_build/dev/ProjectInstructions/addendums/team_start_and_release.md ++++ b/docs_build/dev/ProjectInstructions/addendums/team_start_and_release.md +@@ -20,6 +20,17 @@ Before a team starts, validate: + - assigned team uses NATO phonetic naming + - work remains with the assigned team until complete or OWNER reassignment + ++## Current Four-Team Start Set ++ ++The current active delivery teams for backlog start commands are: ++ ++- Team Alfa ++- Team Bravo ++- Team Charlie ++- Team Delta ++ ++Each team start must confirm the selected backlog item is inside that team's ownership area. ++ + ## Assignment Flow + + For backlog-driven work: +diff --git a/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md b/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md +index 09cc34579..0b8942261 100644 +--- a/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md ++++ b/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md +@@ -4,17 +4,17 @@ + + ### Idea + +-0% Complete — Dream, brainstorm, and explore early game concepts ++33% Complete — Dream, brainstorm, and explore early game concepts + +-- [ ] Alfa - Idea Board ++- [x] Alfa - Idea Board + - [ ] Alfa - Game Concept Notes + - [ ] Alfa - Creator Learning + + ### Design + +-0% Complete — Shape the game's story, structure, and systems ++17% Complete — Shape the game's story, structure, and systems + +-- [ ] Alfa - Game Hub ++- [x] Alfa - Game Hub + - [ ] Alfa - Game Design + - [ ] Alfa - Game Configuration + - [ ] Alfa - Game Crew +@@ -159,3 +159,45 @@ + - Open PR, draft PR, local branch, and remote branch review requirements verified. + - Recommendation-only hygiene audit process verified. + - Branch deletion and PR closure remain prohibited without explicit owner approval. ++ ++## Four-Team Backlog Alignment ++ ++### Team Alfa ++ ++- [ ] Alfa - Game Hub polish ++- [ ] Alfa - Game Journey completion tracking ++- [ ] Alfa - Journey progress calculations ++- [ ] Alfa - Creator onboarding flow ++- [ ] Alfa - Game Hub image integration ++ ++### Team Bravo ++ ++- [ ] Bravo - Audio tool improvements ++- [ ] Bravo - Audio Effects tool ++- [ ] Bravo - Messages tool ++- [ ] Bravo - Emotion Profiles ++- [ ] Bravo - TTS Profiles ++- [ ] Bravo - Asset Browser enhancements ++- [ ] Bravo - Vector Art improvements ++- [ ] Bravo - MIDI Studio improvements ++ ++### Team Charlie ++ ++- [ ] Charlie - Guardrail hardening ++- [ ] Charlie - Browser validation hardening ++- [ ] Charlie - Remaining test relocation audit ++- [ ] Charlie - Compliance baseline freeze ++- [ ] Charlie - System Health improvements ++- [ ] Charlie - Infrastructure dashboard ++- [ ] Charlie - Environment validation ++ ++### Team Delta ++ ++- [ ] Delta - Shared JS consolidation ++- [ ] Delta - API client consolidation ++- [ ] Delta - Runtime performance audit ++- [ ] Delta - Engine test coverage improvements ++- [ ] Delta - Event system audit ++- [ ] Delta - Controls runtime framework audit ++- [ ] Delta - Object runtime framework audit ++- [ ] Delta - World runtime framework audit +diff --git a/docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md b/docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md +index 9a3c515f7..acefbba8e 100644 +--- a/docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md ++++ b/docs_build/dev/ProjectInstructions/team_assignments/TEAM_ASSIGNMENTS.md +@@ -57,6 +57,14 @@ Active assignment: none. + + Active branch: none. + ++## Team Delta ++ ++Status: Available ++ ++Active assignment: none. ++ ++Active branch: none. ++ + ## Team OWNER + + Status: Available +diff --git a/docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md b/docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md +index baa1598b6..865cca551 100644 +--- a/docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md ++++ b/docs_build/dev/ProjectInstructions/team_assignments/team_ownership.md +@@ -1,43 +1,56 @@ + # Team Ownership Governance + +-## OWNER ++## Current Four-Team Ownership Model + +-- Architecture +-- Product Direction +-- Cross-Team Governance +-- Final Approval ++This section is the current OWNER-approved active ownership alignment. + + ## Team Alfa + +-- Idea Board + - Game Hub + - Game Journey +-- Design +-- Objects +-- Worlds +-- Interface +-- Controls +-- Rules +-- Progression ++- Idea Board ++- Creator workflow ++- Creator onboarding ++- UX flow + + ## Team Bravo + +-- Graphics + - Audio ++- Audio Effects + - Messages +-- TTS +-- MIDI +-- Publishing +-- Community +-- Marketplace ++- Emotion Profiles ++- TTS Profiles ++- Asset Browser ++- Vector Art ++- MIDI Studio ++- Creator content tools + + ## Team Charlie + +-- Governance ++- Repository compliance ++- Validation + - Infrastructure +-- Operations +-- Diagnostics ++- Storage ++- Environment management + - System Health ++- Operations ++ ++## Team Delta ++ ++- Engine ++- Runtime ++- Shared JS ++- API clients ++- Event systems ++- Performance ++- Technical debt remediation ++- Runtime test coverage ++ ++## Current Four-Team Rule ++ ++Alfa, Bravo, Charlie, and Delta are the four active delivery teams for backlog ownership and team start routing. ++ ++Each team may pull only from its ownership area unless OWNER explicitly reassigns, splits, or approves cross-team work. + + ## Rule + +diff --git a/docs_build/dev/reports/PR_26172_CHARLIE_001-repository-compliance-audit.md b/docs_build/dev/reports/PR_26172_CHARLIE_001-repository-compliance-audit.md +new file mode 100644 +index 000000000..ff09afb85 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26172_CHARLIE_001-repository-compliance-audit.md +@@ -0,0 +1,236 @@ ++# PR_26172_CHARLIE_001 Repository Compliance Audit ++ ++## Scope ++ ++Audit repository compliance against: ++ ++- PR_034 Canonical Repository Structure ++- PR_035 Test Structure Standardization ++- PR_036 Legacy Migration Policy ++ ++Reviewed areas: ++ ++- `toolbox/` ++- `assets/` ++- `tests/` ++- `api/` ++- `serverside/` ++- `src/engine/` ++ ++This PR is audit-only. No executable implementation changes were made. ++ ++## Team Ownership ++ ++- TEAM token: CHARLIE ++- Ownership classification: governance / repository compliance / diagnostics ++- TEAM ownership result: PASS ++ ++## Branch Validation ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Started from latest main | PASS | `main` was pulled before branch creation; source commit `c4a495f0aa8e32d499ca64555c4a3547e6fcb298`. | ++| Worktree clean before branch | PASS | `git status --short` returned no output before branch creation. | ++| Local/origin sync before branch | PASS | `git rev-list --left-right --count HEAD...origin/main` returned `0 0`. | ++| PR branch created from main | PASS | Branch `pr/26172-CHARLIE-001-repository-compliance-audit` was created from latest `main`. | ++| Runtime/source edits avoided | PASS | Planned changed files are reports only under `docs_build/dev/reports/`. | ++ ++## Repository Area Results ++ ++| Area | Status | Findings | ++| --- | --- | --- | ++| `toolbox/` | FAIL | Active tool JavaScript remains beside tool HTML entries instead of canonical `assets/toolbox/{tool-name}/js/index.js` or shared asset roots. | ++| `assets/` | PASS | No non-compliant JS or CSS files were found in `assets/`; scanned JS/CSS lives under `assets/theme-v2/`. | ++| `tests/` | FAIL | 494 files are in non-canonical top-level test locations outside `tests/toolbox/`, `tests/engine/`, `tests/api/`, `tests/server/`, `tests/js/shared/`, or `tests/regression/`. | ++| `api/` | FAIL | Canonical root is absent. No API files were found to classify, but the expected top-level API area is not physically present. | ++| `serverside/` | FAIL | Canonical root is absent. No serverside files were found to classify, but the expected top-level serverside area is not physically present. | ++| `src/engine/` | FAIL | Most engine JS is under feature folders, but `src/engine/paletteList.js` is a root-level JS file and `src/engine/ui/*.css` contains CSS outside canonical asset/theme roots. | ++ ++## Non-Compliant JS Locations ++ ++### `toolbox/` ++ ++These active JavaScript files are outside the canonical tool asset structure: ++ ++- `toolbox/assets/assets-api-client.js` ++- `toolbox/assets/assets-upload-worker.js` ++- `toolbox/assets/assets.js` ++- `toolbox/colors/colors.js` ++- `toolbox/colors/palette-api-client.js` ++- `toolbox/controls/controls-api-client.js` ++- `toolbox/controls/controls.js` ++- `toolbox/game-configuration/game-configuration-api-client.js` ++- `toolbox/game-configuration/game-configuration.js` ++- `toolbox/game-design/game-design-api-client.js` ++- `toolbox/game-design/game-design.js` ++- `toolbox/game-hub/game-hub-api-client.js` ++- `toolbox/game-hub/game-hub.js` ++- `toolbox/game-journey/game-journey-api-client.js` ++- `toolbox/game-journey/game-journey.js` ++- `toolbox/idea-board/index.js` ++- `toolbox/messages/message-tts-service-registry.js` ++- `toolbox/messages/messages-api-client.js` ++- `toolbox/messages/messages.js` ++- `toolbox/objects/objects-api-client.js` ++- `toolbox/objects/objects.js` ++- `toolbox/tags/tags-api-client.js` ++- `toolbox/tags/tags.js` ++- `toolbox/text-to-speech/text2speech.js` ++- `toolbox/tool-registry-api-client.js` ++- `toolbox/toolRegistry.js` ++- `toolbox/tools-page-accordions.js` ++ ++Recommended target pattern: ++ ++- Tool-specific JS: `assets/toolbox/{tool-name}/js/index.js` ++- Shared toolbox JS: `assets/js/shared/` ++ ++### `src/engine/` ++ ++- `src/engine/paletteList.js` ++ ++Recommended target pattern: ++ ++- Move under a feature folder such as `src/engine/palette/` after import-impact review. ++ ++## Non-Compliant CSS Locations ++ ++### `toolbox/` ++ ++- None found. ++ ++### `assets/` ++ ++- None found. ++ ++### `src/engine/` ++ ++These CSS files are active style assets under engine source: ++ ++- `src/engine/ui/baseLayout.css` ++- `src/engine/ui/hubCommon.css` ++- `src/engine/ui/spriteEditor.css` ++ ++Recommended target pattern: ++ ++- Move shared UI/theme styling into `assets/theme-v2/css/` or create an approved engine UI style policy before relocation. ++ ++## Non-Compliant Test Locations ++ ++Canonical test roots from PR_035: ++ ++- `tests/toolbox/{tool-name}/` ++- `tests/engine/{feature-name}/` ++- `tests/api/{feature-name}/` ++- `tests/server/{feature-name}/` ++- `tests/js/shared/` ++- `tests/regression/` ++ ++The audit found 494 files in non-canonical test locations: ++ ++| Path | File Count | Examples | ++| --- | ---: | --- | ++| `tests/ai/` | 1 | `tests/ai/AIBehaviors.test.mjs` | ++| `tests/assets/` | 1 | `tests/assets/AssetLoaderSystem.test.mjs` | ++| `tests/audio/` | 1 | `tests/audio/AudioService.test.mjs` | ++| `tests/combat/` | 1 | `tests/combat/Combat.test.mjs` | ++| `tests/config/` | 1 | `tests/config/ConfigStore.test.mjs` | ++| `tests/core/` | 11 | `tests/core/EngineCoreBoundaryBaseline.test.mjs` | ++| `tests/dev-runtime/` | 31 | `tests/dev-runtime/AdminHealthOperations.test.mjs` | ++| `tests/entity/` | 1 | `tests/entity/Entity.test.mjs` | ++| `tests/events/` | 2 | `tests/events/EventBus.test.mjs` | ++| `tests/final/` | 11 | `tests/final/ReleaseReadinessSystems.test.mjs` | ++| `tests/fixtures/` | 52 | `tests/fixtures/assets/asset-scenarios.json` | ++| `tests/fx/` | 1 | `tests/fx/ParticleSystem.test.mjs` | ++| `tests/games/` | 35 | `tests/games/AsteroidsValidation.test.mjs` | ++| `tests/helpers/` | 11 | `tests/helpers/playwrightRepoServer.mjs` | ++| `tests/index.html` | 1 | `tests/index.html` | ++| `tests/input/` | 8 | `tests/input/InputService.test.mjs` | ++| `tests/persistence/` | 1 | `tests/persistence/StorageService.test.mjs` | ++| `tests/playwright/` | 44 | `tests/playwright/tools/GameJourneyTool.spec.mjs` | ++| `tests/playwright_installation.txt` | 1 | `tests/playwright_installation.txt` | ++| `tests/production/` | 3 | `tests/production/ProductionReadiness.test.mjs` | ++| `tests/README.md` | 1 | `tests/README.md` | ++| `tests/render/` | 1 | `tests/render/Renderer.test.mjs` | ++| `tests/replay/` | 2 | `tests/replay/ReplaySystem.test.mjs` | ++| `tests/results/` | 26 | `tests/results/playwright-results.json` | ++| `tests/run-tests.mjs` | 1 | `tests/run-tests.mjs` | ++| `tests/runtime/` | 81 | `tests/runtime/V2SessionPersistence.test.mjs` | ++| `tests/samples/` | 1 | `tests/samples/FullscreenRuleEnforcement.test.mjs` | ++| `tests/scenes/` | 3 | `tests/scenes/SceneManager.test.mjs` | ++| `tests/schemas/` | 1 | `tests/schemas/tool.manifest.schema.json` | ++| `tests/shared/` | 92 | `tests/shared/ProjectContract.test.mjs` | ++| `tests/testRunner.html` | 1 | `tests/testRunner.html` | ++| `tests/testRunner.js` | 1 | `tests/testRunner.js` | ++| `tests/tools/` | 57 | `tests/tools/ToolBoundaryEnforcement.test.mjs` | ++| `tests/validation/` | 3 | `tests/validation/samples.runtime.validation.report.json` | ++| `tests/vector/` | 1 | `tests/vector/VectorMath.test.mjs` | ++| `tests/world/` | 4 | `tests/world/WorldSystems.test.mjs` | ++ ++Generated Playwright result artifacts under `tests/results/` should be treated as cleanup/archive candidates rather than active test source. ++ ++## Legacy Migration Candidates ++ ++| Priority | Candidate | Reason | Recommended Handling | ++| --- | --- | --- | --- | ++| P0 | `tests/results/` | Generated result artifacts are tracked under active tests. | Move to ignored output or archive/report storage after owner approval. | ++| P1 | `toolbox/*/*.js` and shared `toolbox/*.js` | Active JS is colocated with HTML entries instead of canonical asset roots. | Migrate tool JS to `assets/toolbox/{tool-name}/js/index.js`; migrate shared JS to `assets/js/shared/`. | ++| P1 | `tests/dev-runtime/`, `tests/playwright/`, `tests/runtime/`, `tests/shared/`, `tests/tools/` | Large active test buckets conflict with PR_035 canonical test roots. | Split by ownership into `tests/toolbox/`, `tests/engine/`, `tests/api/`, `tests/server/`, `tests/js/shared/`, and `tests/regression/`. | ++| P2 | `src/engine/paletteList.js` | Root-level engine JS file is outside `src/engine/{feature-name}/`. | Move to an approved feature folder with import compatibility reviewed. | ++| P2 | `src/engine/ui/*.css` | CSS lives under engine source instead of theme/tool asset roots. | Move to `assets/theme-v2/css/` or define an explicit engine UI CSS policy. | ++| P3 | Missing `api/` and `serverside/` roots | Canonical target roots are absent. | Create roots when the first API/server migration needs them, with placeholder README only if governance permits. | ++ ++## Prioritized Remediation List ++ ++1. Remove or archive tracked generated artifacts under `tests/results/`. ++2. Create a staged migration plan for `toolbox/` JavaScript sidecars, starting with shared files (`toolRegistry.js`, `tool-registry-api-client.js`, `tools-page-accordions.js`). ++3. Standardize high-volume test buckets in phases: `tests/dev-runtime/`, `tests/playwright/`, `tests/runtime/`, `tests/shared/`, then `tests/tools/`. ++4. Move `src/engine/paletteList.js` into an engine feature folder after import-impact review. ++5. Resolve engine CSS placement for `src/engine/ui/*.css`. ++6. Add or defer canonical `api/` and `serverside/` roots with explicit owner-approved scope. ++ ++## Recommended Next Charlie PRs ++ ++- `PR_26172_CHARLIE_002-test-results-artifact-cleanup-plan` ++- `PR_26172_CHARLIE_003-toolbox-js-canonical-asset-migration-plan` ++- `PR_26172_CHARLIE_004-test-structure-standardization-plan` ++- `PR_26172_CHARLIE_005-engine-root-js-and-css-migration-plan` ++- `PR_26172_CHARLIE_006-api-serverside-root-readiness-plan` ++ ++## Requirement Checklist ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Follow ProjectInstructions | PASS | Read active `docs_build/dev/ProjectInstructions/` governance before work. | ++| Start from latest main | PASS | Main pulled and branch created from `c4a495f0aa8e32d499ca64555c4a3547e6fcb298`. | ++| Worktree clean before work | PASS | `git status --short` returned no output before edits. | ++| Audit PR_034 structure | PASS | Compared scoped paths to canonical toolbox, assets, engine, API, and serverside roots. | ++| Audit PR_035 tests | PASS | Compared `tests/` contents to canonical test roots. | ++| Audit PR_036 legacy migration policy | PASS | Classified migration candidates without moving or deleting files. | ++| Review `toolbox/` | PASS | Identified 27 non-compliant JS files. | ++| Review `assets/` | PASS | Found no non-compliant active JS/CSS in `assets/`. | ++| Review `tests/` | PASS | Identified 494 files in non-canonical test locations. | ++| Review `api/` | PASS | Directory absent; recorded as structural failure. | ++| Review `serverside/` | PASS | Directory absent; recorded as structural failure. | ++| Review `src/engine/` | PASS | Identified root JS and engine CSS placement issues. | ++| No executable implementation changes | PASS | Audit/report-only scope. | ++| Create audit report | PASS | This file. | ++| Create standard Codex reports | PASS | `docs_build/dev/reports/codex_review.diff` and `docs_build/dev/reports/codex_changed_files.txt` exist. | ++| Create ZIP artifact | PASS | `tmp/PR_26172_CHARLIE_001-repository-compliance-audit_delta.zip` exists. | ++ ++## Validation Lane Report ++ ++- `git diff --check`: PASS. ++- Report exists: PASS. ++- Required Codex reports exist: PASS. ++- ZIP artifact exists: PASS. ++- Runtime source files changed: PASS, changed files are limited to `docs_build/dev/reports/`. ++- Playwright: SKIP, audit-only documentation/report PR. ++- Samples: SKIP, audit-only documentation/report PR. ++ ++## Manual Validation Notes ++ ++- The audit intentionally did not move files, delete generated artifacts, create canonical roots, or modify executable source. ++- `api/` and `serverside/` were missing from the working tree; this was recorded as a structural finding, not remediated in this PR. ++- Non-canonical test locations were counted by top-level path under `tests/` to keep the report actionable without rewriting the test tree. ++- Tracked generated artifacts under `tests/results/` are listed as a high-priority cleanup candidate because they are active repository files but not active test source. +diff --git a/docs_build/dev/reports/PR_26172_CHARLIE_002-test-results-artifact-cleanup.md b/docs_build/dev/reports/PR_26172_CHARLIE_002-test-results-artifact-cleanup.md +new file mode 100644 +index 000000000..17898495f +--- /dev/null ++++ b/docs_build/dev/reports/PR_26172_CHARLIE_002-test-results-artifact-cleanup.md +@@ -0,0 +1,129 @@ ++# PR_26172_CHARLIE_002 Test Results Artifact Cleanup ++ ++## Scope ++ ++Clean up generated test result artifacts under `tests/results/` after the Charlie repository compliance audit identified that path as a high-priority cleanup candidate. ++ ++Source audit: ++ ++- `docs_build/dev/reports/PR_26172_CHARLIE_001-repository-compliance-audit.md` ++ ++This PR does not modify runtime source and does not move unrelated tests. ++ ++## Team Ownership ++ ++- TEAM token: CHARLIE ++- Ownership classification: governance / repository hygiene / diagnostics ++- TEAM ownership result: PASS ++ ++## Branch Validation ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Started from latest main | PASS | `main` was pulled before branch creation; source commit `f2b50ac9d79256df3a7716ac4eff21f3a4303bb3`. | ++| Worktree clean before branch | PASS | `git status --short` returned no output before branch creation. | ++| Local/origin sync before branch | PASS | `git rev-list --left-right --count HEAD...origin/main` returned `0 0`. | ++| PR branch created from main | PASS | Branch `pr/26172-CHARLIE-002-test-results-artifact-cleanup` was created from latest `main`. | ++ ++## Files Reviewed ++ ++`git ls-files tests/results` returned no tracked files. ++ ++The local ignored `tests/results/` folder contained generated Playwright/report output: ++ ++- `tests/results/artifacts/.last-run.json` ++- `tests/results/artifacts/tools-MidiStudioV2-MIDI-St-3c5a9-multi-song-manifest-payload-playwright/trace.zip` ++- `tests/results/artifacts/tools-MidiStudioV2-MIDI-St-752e4-on-and-timeline-scroll-sync-playwright/trace.zip` ++- `tests/results/artifacts/tools-MidiStudioV2-MIDI-St-c50c5-m-Tool-Mode-standalone-save-playwright/trace.zip` ++- `tests/results/playwright-results.json` ++- `tests/results/report/data/09daf0cfe8750af5e9e5bb22161367f97296f4fd.zip` ++- `tests/results/report/data/a9ba8bc1c6a629055b981a6f385fa4de3e42a79d.zip` ++- `tests/results/report/data/b1dc1da730cbd5e9adc334a6f385fa4de3e42a79d.zip` ++- `tests/results/report/data/c150573559f5367f4ec5724abb7a55798abcdff9.zip` ++- `tests/results/report/index.html` ++- `tests/results/report/trace/assets/codeMirrorModule-DS0FLvoc.js` ++- `tests/results/report/trace/assets/defaultSettingsView-GTWI-W_B.js` ++- `tests/results/report/trace/codeMirrorModule.DYBRYzYX.css` ++- `tests/results/report/trace/codicon.DCmgc-ay.ttf` ++- `tests/results/report/trace/defaultSettingsView.B4dS75f0.css` ++- `tests/results/report/trace/index.C5466mMT.js` ++- `tests/results/report/trace/index.CzXZzn5A.css` ++- `tests/results/report/trace/index.html` ++- `tests/results/report/trace/manifest.webmanifest` ++- `tests/results/report/trace/playwright-logo.svg` ++- `tests/results/report/trace/snapshot.html` ++- `tests/results/report/trace/sw.bundle.js` ++- `tests/results/report/trace/uiMode.Btcz36p_.css` ++- `tests/results/report/trace/uiMode.Vipi55dB.js` ++- `tests/results/report/trace/uiMode.html` ++- `tests/results/report/trace/xtermModule.DYP7pi_n.css` ++ ++## Files Removed Or Retained ++ ++| Category | Status | Notes | ++| --- | --- | --- | ++| Tracked files under `tests/results/` | None removed | No tracked files existed under `tests/results/`. | ++| Local ignored generated artifacts under `tests/results/` | Removed from workspace | Removed only after verifying the resolved target path was inside the repository. | ++| Active test source | Retained | No active test source was found under `tests/results/`. | ++| Fixture or baseline data | Retained | No committed fixture or baseline dependency was found under `tests/results/`. | ++ ++## Reference And Dependency Check ++ ++| Check | Status | Evidence | ++| --- | --- | --- | ++| Active tracked files under `tests/results/` | PASS | `git ls-files tests/results` returned no output. | ++| Tracked ignored files under `tests/results/` | PASS | `git ls-files -c -i --exclude-standard tests/results` returned no output. | ++| Ignored local generated files under `tests/results/` | PASS | `git ls-files -o -i --exclude-standard tests/results` listed only Playwright/report artifacts. | ++| Active config uses `tmp/test-results/` | PASS | `playwright.config.cjs` writes output, artifacts, HTML report, and JSON report under `tmp/test-results/`. | ++| Active references to `tests/results/` | PASS | Active config/test/docs search returned no required source or fixture dependency. | ++| Historical references retained | PASS | References in `archive/` and historical `docs_build/dev/reports/` were not modified. | ++ ++## Ignore Rule Changes ++ ++Updated `.gitignore` to make generated test-output protection explicit: ++ ++- Kept `tests/results/`. ++- Added `tests/results/**`. ++- Kept `tmp/test-results/`. ++- Added `tmp/test-results/**`. ++- Confirmed `tmp/` remains ignored. ++ ++Ignore probe: ++ ++- `git check-ignore -v tests/results/probe.txt` resolves to `.gitignore`. ++- `git check-ignore -v tmp/test-results/probe.txt` resolves to `.gitignore`. ++ ++## Requirement Checklist ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Confirm Project Instructions were reviewed | PASS | Read `docs_build/dev/ProjectInstructions/README.txt`, `PROJECT_INSTRUCTIONS.md`, branch/workflow governance, team ownership, and artifact/reporting rules. | ++| Use PR_26172_CHARLIE_001 findings | PASS | This cleanup is based on the P0 `tests/results/` finding. | ++| Review `tests/results/` | PASS | Reviewed tracked, ignored, and local generated contents. | ++| Confirm generated artifacts, not active source | PASS | Files were Playwright JSON, HTML report, trace assets, and zipped trace/report data. | ++| Search references to `tests/results/` files | PASS | No active source/fixture dependency found; historical references retained. | ++| Remove tracked generated artifacts if safe | PASS | No tracked generated artifacts existed to remove. | ++| Add/update ignore rules | PASS | `.gitignore` now explicitly includes `tests/results/**` and `tmp/test-results/**`. | ++| Do not remove active test source | PASS | No active test source removed. | ++| Do not modify runtime source | PASS | No runtime source changed. | ++| Do not move unrelated tests | PASS | No test files were moved. | ++| Stop gate not triggered | PASS | No `tests/results/` file was required as active source, fixture data, or committed baseline data. | ++| Create required reports | PASS | `docs_build/dev/reports/codex_review.diff` and `docs_build/dev/reports/codex_changed_files.txt` exist. | ++| Create ZIP artifact | PASS | `tmp/PR_26172_CHARLIE_002-test-results-artifact-cleanup_delta.zip` exists. | ++ ++## Validation Lane Report ++ ++- `git diff --check`: PASS. ++- Cleanup limited to generated artifacts under `tests/results/`: PASS. ++- Ignore rule prevents recommit: PASS. ++- Runtime source files changed: PASS, no runtime source files changed. ++- Required reports exist: PASS. ++- ZIP artifact exists: PASS. ++- Playwright: SKIP, ignore/report-only cleanup with no active test or runtime source changes. ++- Samples: SKIP, no sample files changed. ++ ++## Manual Validation Notes ++ ++- The local ignored `tests/results/` directory was deleted from the workspace only after path verification showed it was inside the repository root. ++- Repository history already contains `docs_build/dev/reports/docs_archive_test_output_cleanup_report.md`, which documents the prior migration of generated test output from `tests/results/` to `tmp/test-results/`. ++- This PR preserves historical report/archive references and only hardens the active ignore rule. +diff --git a/docs_build/dev/reports/PR_26172_CHARLIE_003-src-dev-runtime-test-migration-audit.md b/docs_build/dev/reports/PR_26172_CHARLIE_003-src-dev-runtime-test-migration-audit.md +new file mode 100644 +index 000000000..3a9529821 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26172_CHARLIE_003-src-dev-runtime-test-migration-audit.md +@@ -0,0 +1,94 @@ ++# PR_26172_CHARLIE_003 Src Dev-Runtime Test Migration Audit ++ ++## Scope ++ ++Audit `src/dev-runtime/` for test files that should move into canonical test paths. ++ ++Canonical test targets reviewed: ++ ++- `tests/server/` ++- `tests/api/` ++- `tests/engine/` ++- `tests/toolbox/` ++ ++This PR is audit-only. No files were moved and no runtime source was modified. ++ ++## Branch Validation ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Started from latest main | PASS | Workstream branch was created from `main` at `b97893c78dcfed05d5b0de0c7d03127ec5575292`. | ++| Worktree clean before branch | PASS | `git status --short` returned no output before branch creation. | ++| Local/origin sync before branch | PASS | `git rev-list --left-right --count HEAD...origin/main` returned `0 0`. | ++| Active branch | PASS | `PR_26172_CHARLIE_repository-compliance-stack`. | ++| Team ownership | PASS | Team Charlie owns governance, infrastructure, operations, diagnostics, and system health. | ++ ++## Files Reviewed ++ ++`src/dev-runtime/` currently contains runtime, server, persistence, admin, storage, seed, auth, and dev utility modules. ++ ++Test-pattern scans found: ++ ++- `rg --files src/dev-runtime -g "*.test.*" -g "*.spec.*" -g "*Test*" -g "*Spec*"`: no matches. ++- `rg -n "node:test|describe\\(|it\\(|test\\(|assert\\." src/dev-runtime -g "*.js" -g "*.mjs" -g "*.cjs"`: no test harness matches. ++ ++The `src/dev-runtime/testing/` directory was reviewed specifically: ++ ++- `src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs` ++- `src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs` ++ ++These are DEV-only utility/runtime support modules, not test files. They are imported by scripts and covered by tests under `tests/dev-runtime/`. ++ ++## Matching Canonical Target Paths ++ ++No direct file moves are recommended because no test files live under `src/dev-runtime/`. ++ ++Existing coverage already lives in active test locations: ++ ++| Runtime utility | Existing coverage | Canonical target recommendation | ++| --- | --- | --- | ++| `src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs` | `tests/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs` | Future test-structure migration should move coverage to `tests/server/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs` or an owner-approved equivalent under `tests/server/`. | ++| `src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs` | `tests/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs` | Future test-structure migration should move coverage to `tests/server/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs` or an owner-approved equivalent under `tests/server/`. | ++ ++## Next Migration PRs ++ ++Exact next PR recommendations: ++ ++1. `PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move` ++ - Expected result: STOP GATE unless new low-risk `src/dev-runtime` test files appear before execution. ++ - Reason: this audit found no movable tests under `src/dev-runtime/`. ++2. `PR_26172_CHARLIE_010-dev-runtime-test-structure-plan` ++ - Scope: define the owner-approved canonical destination for existing `tests/dev-runtime/` coverage. ++ - Reason: the broader test-standardization issue is under `tests/dev-runtime/`, not under `src/dev-runtime/`. ++3. `PR_26172_CHARLIE_011-dev-runtime-server-test-folder-migration` ++ - Scope: move clear server/dev-runtime tests from `tests/dev-runtime/` into `tests/server/dev-runtime/` after the canonical destination is approved. ++ ++## Requirement Checklist ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Review and use ProjectInstructions | PASS | Read active ProjectInstructions, branch governance, team ownership, and artifact/reporting rules. | ++| Review `src/dev-runtime/` | PASS | Scoped file and content scans completed. | ++| Identify test files under `src/dev-runtime/` | PASS | No test files found. | ++| Identify matching canonical target paths | PASS | No direct move targets; future coverage targets documented for existing `tests/dev-runtime/` files. | ++| Do not move files | PASS | No files moved. | ++| Do not modify runtime source | PASS | Runtime source unchanged. | ++| Confirm no executable runtime source changed | PASS | Changed files are reports only for this PR. | ++| Audit lists exact next migration PRs | PASS | See "Next Migration PRs". | ++| ZIP exists | PASS | `tmp/PR_26172_CHARLIE_003-src-dev-runtime-test-migration-audit_delta.zip` exists. | ++ ++## Validation Lane Report ++ ++- No executable runtime source changed: PASS. ++- Audit lists exact next migration PRs: PASS. ++- `git diff --check`: PASS. ++- Required reports exist: PASS. ++- ZIP artifact exists: PASS. ++- Playwright: SKIP, audit-only scope. ++- Samples: SKIP, audit-only scope. ++ ++## Manual Validation Notes ++ ++- The word `testing` in `src/dev-runtime/testing/` is directory naming for DEV-only utility modules, not evidence of colocated test source. ++- Existing active tests for those utilities are already outside `src/dev-runtime/`, under `tests/dev-runtime/`. ++- PR_004 should stop-gate if no new low-risk source-path test files are present when it runs. +diff --git a/docs_build/dev/reports/PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move.md b/docs_build/dev/reports/PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move.md +new file mode 100644 +index 000000000..ec6a12d10 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move.md +@@ -0,0 +1,58 @@ ++# PR_26172_CHARLIE_004 Src Dev-Runtime Low-Risk Test Move ++ ++## Scope ++ ++Move only low-risk `src/dev-runtime` test files into canonical `tests/` paths. ++ ++## Stop Gate Result ++ ++Status: STOP GATE - no low-risk moves exist. ++ ++Reason: ++ ++- PR_26172_CHARLIE_003 found no test files under `src/dev-runtime/`. ++- PR_004 re-ran the low-risk eligibility check and found no `*.test.*`, `*.spec.*`, `*Test*`, or `*Spec*` files under `src/dev-runtime/`. ++- The two files under `src/dev-runtime/testing/` are DEV-only utility modules imported by scripts, not test source. ++ ++Because there are no source-path test files, moving files would either move runtime/dev utility code or expand scope into broader `tests/dev-runtime/` standardization. Both are outside this PR. ++ ++## Files Moved ++ ++None. ++ ++## Retained Files ++ ++| Path | Reason Retained | ++| --- | --- | ++| `src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs` | DEV-only cleanup utility; not a test file. | ++| `src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs` | DEV-only seed sync utility; not a test file. | ++ ++## Validation ++ ++| Check | Status | Evidence | ++| --- | --- | --- | ++| PR_003 audit used | PASS | PR_003 report found no movable source-path tests. | ++| Low-risk move scan rerun | PASS | `rg --files src/dev-runtime -g "*.test.*" -g "*.spec.*" -g "*Test*" -g "*Spec*"` returned no matches. | ++| Runtime source unchanged | PASS | No runtime source files changed. | ++| Old paths no longer referenced | N/A | No files were moved, so no path reference update was required. | ++| New paths follow canonical tests roots | N/A | No files were moved. | ++| Targeted tests for moved files | SKIP | No files were moved. | ++| ZIP exists | PASS | `tmp/PR_26172_CHARLIE_004-src-dev-runtime-low-risk-test-move_delta.zip` exists. | ++ ++## Requirement Checklist ++ ++| Requirement | Status | Evidence | ++| --- | --- | --- | ++| Use PR_003 audit | PASS | PR_003 was the input for this stop gate. | ++| Move only obvious low-risk files | PASS | No candidates existed, so no move was attempted. | ++| Update package/test references if required | N/A | No move occurred. | ++| Do not change test logic unless imports/paths require it | PASS | No test logic changed. | ++| Do not modify runtime source | PASS | Runtime source unchanged. | ++| Do not move ambiguous files | PASS | No ambiguous files moved. | ++| Continue to next audit PR if safe | PASS | Next scope is audit-only and independent. | ++ ++## Manual Validation Notes ++ ++- This is a successful stop-gate deliverable, not a failed PR. ++- The broader migration target is existing `tests/dev-runtime/` coverage, which should be planned separately under an owner-approved canonical destination. ++- No executable implementation files were changed. +diff --git a/docs_build/dev/reports/PR_26172_CHARLIE_005-scattered-tool-js-css-audit.md b/docs_build/dev/reports/PR_26172_CHARLIE_005-scattered-tool-js-css-audit.md +new file mode 100644 +index 000000000..e4f37ddca +--- /dev/null ++++ b/docs_build/dev/reports/PR_26172_CHARLIE_005-scattered-tool-js-css-audit.md +@@ -0,0 +1,107 @@ ++# PR_26172_CHARLIE_005 Scattered Tool JS/CSS Audit ++ ++## Scope ++ ++Audit `toolbox/` and `assets/` for non-canonical tool JavaScript and CSS locations against the canonical repository structure. ++ ++Canonical tool asset targets: ++ ++- `assets/toolbox/{tool}/js/index.js` ++- `assets/toolbox/{tool}/css/index.css` ++ ++This PR is audit-only. No implementation files were moved. ++ ++## Summary ++ ++| Finding | Count | Status | ++| --- | ---: | --- | ++| Toolbox HTML pages reviewed | 50 | PASS | ++| Tool JS files under `toolbox/` | 27 | FAIL | ++| Tool CSS files under `toolbox/` | 0 | PASS | ++| Canonical tool JS files under `assets/toolbox/` | 0 | FAIL | ++| Canonical tool CSS files under `assets/toolbox/` | 0 | N/A | ++| Non-canonical tool JS/CSS under `assets/toolbox/` | 0 | PASS | ++| Inline `
Database Health - Postgres Only