Skip to content

Fix change field handling of narration (BL-16368)#7939

Draft
JohnThomson wants to merge 2 commits into
masterfrom
keepAudioChangingField
Draft

Fix change field handling of narration (BL-16368)#7939
JohnThomson wants to merge 2 commits into
masterfrom
keepAudioChangingField

Conversation

@JohnThomson

@JohnThomson JohnThomson commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
  • changing to None, or something that doesn't have text already: keeps the current text and any recording, but recording get a new ID and is no longer locked (or duplicated)
  • changing to something that has a value: also gets any existing recording.

This change is Reviewable

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/BloomExe/web/controllers/EditingViewApi.cs">

<violation number="1" location="src/BloomExe/web/controllers/EditingViewApi.cs:162">
P2: Do not interpolate request parameters directly into XPath; malformed or crafted values can break or alter the query.</violation>
</file>

<file name="src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts">

<violation number="1" location="src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts:6242">
P1: The new audio-independence logic skips `TextBox` recordings, so some kept recordings may retain shared ids instead of being duplicated with a new id.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +6242 to +6243
editable.classList.contains("audio-sentence") &&
editable.getAttribute("id")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The new audio-independence logic skips TextBox recordings, so some kept recordings may retain shared ids instead of being duplicated with a new id.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts, line 6242:

<comment>The new audio-independence logic skips `TextBox` recordings, so some kept recordings may retain shared ids instead of being duplicated with a new id.</comment>

<file context>
@@ -6225,6 +6228,36 @@ export class CanvasElementManager {
+
+        const audioElements: Element[] = [];
+        if (
+            editable.classList.contains("audio-sentence") &&
+            editable.getAttribute("id")
+        ) {
</file context>
Suggested change
editable.classList.contains("audio-sentence") &&
editable.getAttribute("id")
(editable.classList.contains("audio-sentence") ||
editable.getAttribute("data-audiorecordingmode") ===
"TextBox") &&
editable.getAttribute("id")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a hallucination. The new logic calls makeEditableAudioIndependent(), which includes the passed element in the list it passes to copySoundFileAndAttributes(). The latter does copy ids and various other attributes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I've saved this as a new learning to improve future reviews.

request.ReplyWithText(value);
var matchingDataDivElement =
View.Model.CurrentBook.RawDom.SelectSingleNode(
$"//div[@id='bloomDataDiv']/div[@data-book='{dataBook}' and @lang='{lang}']"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Do not interpolate request parameters directly into XPath; malformed or crafted values can break or alter the query.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/BloomExe/web/controllers/EditingViewApi.cs, line 162:

<comment>Do not interpolate request parameters directly into XPath; malformed or crafted values can break or alter the query.</comment>

<file context>
@@ -157,7 +157,32 @@ private void HandleGetDataBookValue(ApiRequest request)
-            request.ReplyWithText(value);
+            var matchingDataDivElement =
+                View.Model.CurrentBook.RawDom.SelectSingleNode(
+                    $"//div[@id='bloomDataDiv']/div[@data-book='{dataBook}' and @lang='{lang}']"
+                ) as SafeXmlElement;
+
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's saying we should not put {dataBook} and {lang} into the xpath because someone might find a way to use fake values to inject something dangerous. But we do this all over, and I'm not sure what reasonable alternative there might be here. I don't think it's worth fixing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the context—understood this isn’t a change you want to make here.

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JohnThomson made 2 comments.
Reviewable status: 0 of 3 files reviewed, 2 unresolved discussions.

Comment on lines +6242 to +6243
editable.classList.contains("audio-sentence") &&
editable.getAttribute("id")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a hallucination. The new logic calls makeEditableAudioIndependent(), which includes the passed element in the list it passes to copySoundFileAndAttributes(). The latter does copy ids and various other attributes.

request.ReplyWithText(value);
var matchingDataDivElement =
View.Model.CurrentBook.RawDom.SelectSingleNode(
$"//div[@id='bloomDataDiv']/div[@data-book='{dataBook}' and @lang='{lang}']"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's saying we should not put {dataBook} and {lang} into the xpath because someone might find a way to use fake values to inject something dangerous. But we do this all over, and I'm not sure what reasonable alternative there might be here. I don't think it's worth fixing.

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew-polk reviewed 3 files and all commit messages, and made 2 comments.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on JohnThomson).


src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx line 1039 at r1 (raw file):

                                    ? { content: result.data }
                                    : result.data;
                            const content = response.content;

Is this just defensive code the agent added for theoretical backward compatibility? Can't we just remove it since we control the data source?


src/BloomExe/web/controllers/EditingViewApi.cs line 183 at r1 (raw file):

                    hasBloomPostAudioSplitClass = matchingDataDivElement
                        ?.GetAttribute("class")
                        ?.Contains("bloom-postAudioSplit") == true,

Use SafeXmlElement.HasClass

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the card number to the commit message.

@andrew-polk made 1 comment.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on JohnThomson).

- changing to None, or something that doesn't have text already: keeps the current text and any recording, but recording get a new ID and is no longer locked (or duplicated)
- changing to something that has a value: also gets any existing recording.
@JohnThomson JohnThomson force-pushed the keepAudioChangingField branch from 1e6fc12 to 9aff65d Compare June 8, 2026 21:55

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry. Done.

@JohnThomson made 3 comments and resolved 2 discussions.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on andrew-polk).


src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx line 1039 at r1 (raw file):

Previously, andrew-polk wrote…

Is this just defensive code the agent added for theoretical backward compatibility? Can't we just remove it since we control the data source?

Done.


src/BloomExe/web/controllers/EditingViewApi.cs line 183 at r1 (raw file):

Previously, andrew-polk wrote…

Use SafeXmlElement.HasClass

Done.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes how switching a canvas field type's narration setting is handled: changing to "None" (or a type with no existing text) now gives the editable a fresh audio ID while preserving its current content, and changing to a field type that has a data-book value copies both the text and any associated audio recording identity.

  • getDataBookValue now returns a JSON object that includes audio attributes (id, recording mode, duration, end-times, MD5, classes) from the matching bloomDataDiv element, so the client can fully replicate the audio identity.
  • setEditableContentFromKnownDataBookValueIfAny becomes async and returns a boolean indicating whether the existing content was kept; callers use this to decide whether to call the new makeEditableAudioIndependent helper.
  • makeEditableAudioIndependent assigns a new XHTML-safe ID to every audio-sentence element in the editable and asynchronously copies the corresponding audio file, decoupling the recording from any shared source.

Important Files Changed

Filename Overview
src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx Refactors field-type and None onClick handlers to use async/await inside wrapWithRequestPageContentDelay, adds applyEditableAudioIdentityFromDataBookValue to sync audio attributes from the data book response, and makes setEditableContentFromKnownDataBookValueIfAny return a boolean so callers can conditionally call makeEditableAudioIndependent. Logic looks correct and async DOM changes are properly guarded.
src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts Promotes copyAnySoundFileAndAttributesForEditable to public and adds makeEditableAudioIndependent; the new method correctly captures the old id before overwriting it. Two C#-style XML doc comments were added that won't be processed by TypeScript tooling.
src/BloomExe/web/controllers/EditingViewApi.cs Extends getDataBookValue to return a JSON object including audio identity attributes from the matching bloomDataDiv element. The XPath query interpolates lang and dataBook URL params without sanitisation, which is low-risk in this desktop context but fragile against malformed book attributes.

Reviews (1): Last reviewed commit: "Post-review improvements" | Re-trigger Greptile

request.ReplyWithText(value);
var matchingDataDivElement =
View.Model.CurrentBook.RawDom.SelectSingleNode(
$"//div[@id='bloomDataDiv']/div[@data-book='{dataBook}' and @lang='{lang}']"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 XPath injection risk from unsanitized params

dataBook and lang are inserted directly into the XPath string using string interpolation. A value containing a single quote (e.g. a lang attribute set to en' in a crafted book file) would malform the expression, causing a parse error or — in adversarial input — an injected predicate. Standard language tags never carry single quotes, but a corrupt or hand-crafted book could set unusual lang values.

Consider escaping or validating both parameters before interpolation, or use a parameterised XPath API if the underlying library supports one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew-polk reviewed 2 files and all commit messages, made 1 comment, resolved 3 discussions, and dismissed @greptile-apps[bot] from a discussion.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on JohnThomson).

request.ReplyWithText(value);
var matchingDataDivElement =
View.Model.CurrentBook.RawDom.SelectSingleNode(
$"//div[@id='bloomDataDiv']/div[@data-book='{dataBook}' and @lang='{lang}']"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

@JohnThomson JohnThomson marked this pull request as draft June 16, 2026 19:37
@JohnThomson JohnThomson changed the title Fix change field handling of narration Fix change field handling of narration (BL-16368) Jun 16, 2026
@JohnThomson

Copy link
Copy Markdown
Contributor Author

This PR is a draft because we don't actually want to merge it to master (and it has many conflicts). The conflict resolution resulted in a new PR. Keeping this alive because IF we want to cherry-pick to 6.4, which does not have the changes that caused all the conflicts, this is a better starting point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants