Skip to content

text component codec#81

Merged
hayanesuru merged 21 commits into
mainfrom
text
Jun 30, 2026
Merged

text component codec#81
hayanesuru merged 21 commits into
mainfrom
text

Conversation

@hayanesuru

@hayanesuru hayanesuru commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added richer text/chat/dialog modeling via TextComponent, including ClickEvent and HoverEvent.
    • Introduced TextColor parsing and DecorationMap utilities.
    • Added identifier constructors/converters and float-to-u8 helpers.
  • Bug Fixes

    • Improved robustness in generated NBT/protocol parsing, including updated alias/name normalization and hash behavior.
  • Breaking Changes

    • Major schema/type migrations across NBT and protocol layers (e.g., StringTag/CompoundTag, “raw” component/profile representations, redesigned item/dialog/stack structures).
  • Chores

    • Version bumps, workspace publishing updates, added registry configuration, and expanded Clippy lints/docs.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1620355a-e586-4792-a7f9-b0d7f06fff89

📥 Commits

Reviewing files that changed from the base of the PR and between a73698a and c00ca27.

📒 Files selected for processing (2)
  • haya_nbt/src/stringify.rs
  • haya_text/src/chat.rs

📝 Walkthrough

Walkthrough

Workspace manifests, helper crates, NBT types, protocol schemas, and text schemas are updated to use new raw, ref, and tag-backed representations, with build scripts and derive macros refactored around new lookup and token-generation paths.

Changes

Publication metadata and release settings

Layer / File(s) Summary
Publication metadata and release settings
Cargo.toml, release.toml, haya_*/Cargo.toml, haya_ser*/README.md
Workspace package versions, publish settings, README headings, and release signing flags are updated.

Shared utility APIs

Layer / File(s) Summary
Shared utility APIs
haya_collection/src/lib.rs, haya_ecs/src/lib.rs, haya_ident/src/lib.rs, haya_math/src/lib.rs, haya_mutf8/src/lib.rs, haya_ser/src/*.rs, haya_str/src/lib.rs
Clippy warnings and small helper APIs change across the shared utility crates.

Generators and derive macros

Layer / File(s) Summary
Generators and derive macros
haya_data/build.rs, haya_data/src/lib.rs, haya_nbt/build.rs, haya_ser_macro/src/*.rs
Build scripts, alias hashing, and derive macro token generation are refactored around new intermediate bindings.

NBT model and parsing

Layer / File(s) Summary
NBT model and parsing
haya_nbt/src/*.rs
NBT wrapper types, conversions, string storage, numeric parsing, stringify, and Unicode lookup move to StringTag and CompoundTag.

Protocol raw types and packet schemas

Layer / File(s) Summary
Protocol raw types and packet schemas
haya_protocol/src/*.rs, haya_protocol/src/clientbound*, haya_protocol/src/serverbound*, haya_protocol/src/item_stack*
Protocol raw wrappers, borrowed profile types, packet IDs, and payload schemas switch to raw and reference-backed forms.

Text primitives and events

Layer / File(s) Summary
Text primitives and events
haya_text/src/lib.rs, haya_text/src/color.rs, haya_text/src/decoration.rs, haya_text/src/click_event.rs, haya_text/src/hover_event.rs, haya_text/src/json.rs, haya_text/src/chat.rs
The text crate adds the component model, color and decoration primitives, click and hover events, and JSON escaping support.

Text dialog and profile schemas

Layer / File(s) Summary
Text dialog and profile schemas
haya_text/src/profile.rs, haya_text/src/dialog.rs
Dialog and profile schemas switch to TextComponent, StringTag, and NBT-backed item and profile fields.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

I hop through tags with whiskers bright,
From StringTag leaves to CompoundTag light.
ClickEvent twitches, HoverEvent glows,
TextComponent garden softly grows.
🐇✨ The burrow hums; the compile flows.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch text

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hayanesuru

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
haya_protocol/src/item_stack.rs (1)

122-123: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the optional item stack empty sentinel symmetric.

Line 122 accepts count > 0 with id == item::air, but the writer canonicalizes air to count 0, dropping the patch payload on round-trip. Lines 164 and 189 also only check count == 0, while the reader treats every count <= 0 as empty; a negative public count with a non-air id writes trailing item data that the reader will not consume.

🐛 Proposed fix
             let id = item::read(buf)?;
+            if id == item::air {
+                return Err(Error);
+            }
             let positive = V32::read(buf)?.0 as usize;
             let negative = V32::read(buf)?.0 as usize;
@@
-        if count == 0 || id == item::air {
+        if count <= 0 || id == item::air {
             V32(0).write(w);
@@
-    if count == 0 || id == item::air {
+    if count <= 0 || id == item::air {
         V32(0).len_s()

Also applies to: 164-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_protocol/src/item_stack.rs` around lines 122 - 123, The optional item
stack sentinel handling in ItemStack::read and ItemStack::write is inconsistent:
the reader accepts air with a positive count and treats any count <= 0 as empty,
while the writer only canonicalizes count == 0 to air, which can drop payloads
or leave unread trailing data. Make the empty-stack representation symmetric by
normalizing all non-positive counts to the empty sentinel on write, and ensure
the read/write logic in ItemStack::read, ItemStack::write, and the related count
checks around the optional stack paths treat air and empty counts consistently
so round-tripping preserves or correctly discards payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@haya_data/build.rs`:
- Around line 70-86: The range calculations in build.rs are using the raw byte
count returned by read() instead of the growing data buffer length, so slices
can drift when read() inserts separator newlines. Update the range construction
for reg, flu, blo, ite, ent, and pac to derive each start/end from data.len()
before and after each read call, using the existing read() helper and the same
range variables to keep each generated slice aligned.

In `@haya_data/Cargo.toml`:
- Line 13: The Cargo manifest publish setting uses the wrong registry alias;
update the `publish` entry in `Cargo.toml` to use the canonical crates.io
identifier. In the manifest’s `publish` field, replace the current registry name
with `crates-io` so Cargo recognizes the default registry correctly.

In `@haya_ecs/Cargo.toml`:
- Line 9: The crate is inheriting `publish` from the workspace, but
`workspace.package.publish = false` makes `haya_ecs` non-publishable. Update the
`publish` setting in `Cargo.toml` for this crate to explicitly allow publishing,
or adjust the workspace default and override any internal crates as needed. Use
the `publish.workspace` entry in this manifest and the workspace package publish
configuration to locate the change.

In `@haya_ident/src/lib.rs`:
- Around line 207-222: Identifier::new is incorrectly returning None for
byte-valid paths that exceed the inline HayaStr capacity. Update the constructor
to mirror Ident::to_identifier by falling back to a Heap-backed Inner variant
when HayaStr::copy_from fails, while still rejecting invalid bytes via
is_valid_path. If const-ness must be preserved, keep the current behavior only
if all Identifier::new callers are guaranteed to stay within 31 bytes and
document that constraint.

In `@haya_nbt/Cargo.toml`:
- Line 25: The rand dependency is too loosely specified, which can pull in
unexpected 0.x updates and break reproducibility. In Cargo.toml, update the rand
entry to a pinned minor version based on the currently resolved version in your
environment, and keep it aligned with any code that uses rand APIs so the crate
resolves consistently across builds.

In `@haya_nbt/src/stringify.rs`:
- Around line 557-559: The escape-sequence write in the stringification path is
overwriting the start of the buffer instead of appending to its spare capacity.
In the char-encoding logic inside the stringify routine, adjust the
`char::encode_utf8` call to target the unused tail of `buf` rather than a slice
starting at `buf.as_mut_ptr()`, then extend the length based on the number of
bytes written. Use the existing `buf` handling in `stringify.rs` to locate and
update this append path.

In `@haya_protocol/Cargo.toml`:
- Around line 3-10: The crate-level publish setting is inherited from the
workspace default, so `haya_protocol` is currently configured as
non-publishable. Update the `publish` entry in the crate manifest to explicitly
reflect the intended release behavior for `haya_protocol` rather than relying on
`publish.workspace = true`, and ensure the manifest clearly matches whether this
package should be published or kept internal.

In `@haya_ser_macro/Cargo.toml`:
- Line 11: The Cargo manifest for haya_ser_macro uses an undefined registry name
in its publish setting, so update the package metadata to allow publishing to
the default registry instead of referencing crates.io. Locate the publish field
in Cargo.toml and change it from a registry list to the boolean form, keeping
the rest of the package metadata unchanged.

In `@haya_ser_macro/src/lib.rs`:
- Around line 95-99: Matching on i.data in the serialize path partially moves
the input and makes i unusable afterward; update the logic in the serializer
entry point to match on &i.data so the full ItemStruct/ItemEnum value can still
be passed into serialize::serialize_struct and serialize::serialize_enum. Apply
the same borrowing fix in the deserialize path as well, using the corresponding
deserialize dispatch function(s) in lib.rs to avoid the same compile error
there.

In `@haya_str/src/lib.rs`:
- Around line 148-150: The `as_mut_str` method in `haya_str::Str` is exposing
the full backing buffer instead of the logical string contents. Update it to
build the mutable `str` from only the initialized prefix tracked by `self.len`,
matching the behavior of `len()` and `copy_from`. Use the existing `as_mut_str`
symbol and the `self.len` field to locate the change, and ensure the returned
slice never includes the zero-padding tail.

In `@haya_text/src/chat.rs`:
- Around line 846-866: The decoration parsing in chat.rs is dropping values
because DecorationMap builder methods return a new instance instead of mutating
in place. Update the BOLD, ITALIC, UNDERLINED, STRIKETHROUGH, and OBFUSCATED
branches to assign the result of each with_* call back into style.decorations so
the parsed flags are preserved.

In `@haya_text/src/color.rs`:
- Around line 35-43: The FromStr implementation in color.rs is too permissive
because it parses any hex length after stripping the leading #, so it accepts
inputs like `#abc` and `#0000000`. Update the parsing logic in the FromStr path for
Color to require exactly six hex digits before calling u32::from_str_radix,
matching the Deserialize behavior for `#RRGGBB` and keeping the existing Error
handling unchanged.

In `@haya_text/src/decoration.rs`:
- Around line 29-38: The decoration setters are returning updated values, but
the deserialization code is discarding them, so decoded bold/italic/etc. never
persist; update the call sites that use the with_* methods to assign the
returned DecorationMap back, using the relevant helpers like with_obfuscated and
the other with_* setters. Also fix the None case in with_obfuscated so it does
not set both bits and make is_empty() false; preserve the “unset” state
consistently with the getter behavior.

---

Outside diff comments:
In `@haya_protocol/src/item_stack.rs`:
- Around line 122-123: The optional item stack sentinel handling in
ItemStack::read and ItemStack::write is inconsistent: the reader accepts air
with a positive count and treats any count <= 0 as empty, while the writer only
canonicalizes count == 0 to air, which can drop payloads or leave unread
trailing data. Make the empty-stack representation symmetric by normalizing all
non-positive counts to the empty sentinel on write, and ensure the read/write
logic in ItemStack::read, ItemStack::write, and the related count checks around
the optional stack paths treat air and empty counts consistently so
round-tripping preserves or correctly discards payloads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5bd4174f-a6b5-484b-b602-c6ae753a6db5

📥 Commits

Reviewing files that changed from the base of the PR and between 867bc1c and f34f7ec.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (71)
  • .cargo/config.toml
  • Cargo.toml
  • haya_collection/Cargo.toml
  • haya_collection/src/lib.rs
  • haya_data/Cargo.toml
  • haya_data/build.rs
  • haya_data/src/lib.rs
  • haya_ecs/Cargo.toml
  • haya_ecs/src/lib.rs
  • haya_ident/Cargo.toml
  • haya_ident/src/lib.rs
  • haya_math/Cargo.toml
  • haya_math/src/lib.rs
  • haya_mutf8/Cargo.toml
  • haya_mutf8/src/lib.rs
  • haya_nbt/Cargo.toml
  • haya_nbt/build.rs
  • haya_nbt/src/compound.rs
  • haya_nbt/src/lib.rs
  • haya_nbt/src/list.rs
  • haya_nbt/src/number.rs
  • haya_nbt/src/string.rs
  • haya_nbt/src/stringify.rs
  • haya_nbt/src/unicode.rs
  • haya_palette/Cargo.toml
  • haya_palette/src/lib.rs
  • haya_protocol/Cargo.toml
  • haya_protocol/src/advancement.rs
  • haya_protocol/src/chat.rs
  • haya_protocol/src/clientbound.rs
  • haya_protocol/src/clientbound/common.rs
  • haya_protocol/src/clientbound/game.rs
  • haya_protocol/src/clientbound/login.rs
  • haya_protocol/src/crafting.rs
  • haya_protocol/src/entity.rs
  • haya_protocol/src/entity_data.rs
  • haya_protocol/src/item_stack.rs
  • haya_protocol/src/item_stack/item_attribute_modifiers.rs
  • haya_protocol/src/lib.rs
  • haya_protocol/src/map.rs
  • haya_protocol/src/profile.rs
  • haya_protocol/src/registry.rs
  • haya_protocol/src/serverbound.rs
  • haya_protocol/src/serverbound/game.rs
  • haya_protocol/src/trim.rs
  • haya_protocol/src/types.rs
  • haya_ser/Cargo.toml
  • haya_ser/README.md
  • haya_ser/src/lib.rs
  • haya_ser/src/read.rs
  • haya_ser/src/reader.rs
  • haya_ser/src/varint.rs
  • haya_ser/src/write.rs
  • haya_ser_macro/Cargo.toml
  • haya_ser_macro/README.md
  • haya_ser_macro/src/deserialize.rs
  • haya_ser_macro/src/lib.rs
  • haya_ser_macro/src/serialize.rs
  • haya_str/Cargo.toml
  • haya_str/src/lib.rs
  • haya_text/Cargo.toml
  • haya_text/src/chat.rs
  • haya_text/src/click_event.rs
  • haya_text/src/color.rs
  • haya_text/src/decoration.rs
  • haya_text/src/dialog.rs
  • haya_text/src/hover_event.rs
  • haya_text/src/json.rs
  • haya_text/src/lib.rs
  • haya_text/src/profile.rs
  • release.toml
💤 Files with no reviewable changes (1)
  • release.toml

Comment thread haya_data/build.rs Outdated
Comment thread haya_data/Cargo.toml
Comment thread haya_ecs/Cargo.toml
Comment thread haya_ident/src/lib.rs Outdated
Comment thread haya_nbt/Cargo.toml
Comment thread haya_ser_macro/src/lib.rs Outdated
Comment thread haya_str/src/lib.rs
Comment thread haya_text/src/chat.rs
Comment thread haya_text/src/color.rs
Comment thread haya_text/src/decoration.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
haya_text/src/chat.rs (3)

335-351: 🎯 Functional Correctness | 🟡 Minor

Accept Tag::Short in numeric shadow-color decoding.

The Tag enum in haya_nbt defines Short(i16) as a standard NBT primitive. The current deserialize implementation in haya_text/src/chat.rs handles Tag::Byte but omits Tag::Short, causing valid NBT short values to be rejected while narrower byte values are accepted. This creates an inconsistent handling of numeric types.

Proposed fix
             Tag::Byte(value) => Ok(Self {
                 value: value as u32,
             }),
+            Tag::Short(value) => Ok(Self {
+                value: value as u32,
+            }),
             Tag::Long(value) => Ok(Self {
                 value: value as u32,
             }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_text/src/chat.rs` around lines 335 - 351, The numeric shadow-color
deserialization in deserialize currently accepts Tag::Byte but rejects the
standard NBT Tag::Short variant, so update the match in this function to handle
Tag::Short the same way as the other numeric cases. Keep the existing conversion
to u32 and preserve the current behavior for the other Tag variants in
haya_text/src/chat.rs, using deserialize as the location to add the missing
case.

537-557: 🎯 Functional Correctness | 🟠 Major

The current deserialization logic for ListTag incorrectly rejects valid homogeneous string lists, causing failures when parsing translate.with and extra fields that contain string arrays.

The haya_nbt crate correctly parses string lists as ListTag::String, but TextComponent::deserialize in haya_text/src/chat.rs only handles ListTag::Compound at the following locations:

  • Lines 537-557: Tag::List handling in the main match
  • Lines 610-620: translate.with handling
  • Lines 827-836: extra handling

This results in Err returns for valid NBT structures. Update the match arms to handle ListTag::String by converting each string to a literal TextComponent (e.g., Content::Literal).

Required Logic Change

Current (incorrect):

Tag::List(v) => match v {
    ListTag::Compound(_) => { /* ... */ }
    // Missing: ListTag::String(_)
    _ => return Err(...),
}

Proposed (correct):

Tag::List(v) => match v {
    ListTag::Compound(list) => { /* existing compound logic */ }
    ListTag::String(strs) => {
        // Treat each string as a literal component
        let mut comp = Self::deserialize(Tag::Compound(/* empty or minimal */))?; // Or construct literal directly
        strs.into_iter().for_each(|s| {
             comp.siblings.push(Self::new_literal(s)); // Or equivalent
        });
        Ok(comp)
    }
    _ => return Err(...),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_text/src/chat.rs` around lines 537 - 557, TextComponent::deserialize
currently only accepts ListTag::Compound for NBT list fields, so valid
homogeneous string arrays in translate.with and extra still fail. Update the
Tag::List match in TextComponent::deserialize, plus the translate.with and extra
handling branches, to also accept ListTag::String and convert each string entry
into a literal TextComponent/Content::Literal sibling instead of returning Err.
Keep the existing compound-list behavior unchanged and use the same
deserialization path where possible so both compound and string list forms are
supported.

425-428: 🩺 Stability & Availability | 🟡 Minor

Replace the unsafe unreachable_unchecked() with a safe unreachable!() macro.

TextComponent::serialize() is guaranteed to return Tag::Compound based on its implementation. However, using unsafe { core::hint::unreachable_unchecked() } introduces undefined behavior (UB) if this invariant ever drifts. Swapping to the safe unreachable! macro maintains correctness while providing a panic with a helpful message instead of UB if the invariant is violated.

Proposed fix
                         for arg in args {
                             vec.push(match arg.serialize() {
                                 Tag::Compound(x) => x,
-                                _ => unsafe { core::hint::unreachable_unchecked() },
+                                _ => unreachable!("TextComponent::serialize must return a compound tag"),
                             });
                         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_text/src/chat.rs` around lines 425 - 428, The match inside the argument
serialization path in TextComponent::serialize currently uses unsafe
core::hint::unreachable_unchecked() for the non-Compound case. Replace that
unsafe fallback with a safe unreachable!() macro, keeping the same match on
arg.serialize() but adding a clear panic message so the invariant is enforced
without introducing UB.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@haya_text/src/chat.rs`:
- Around line 335-351: The numeric shadow-color deserialization in deserialize
currently accepts Tag::Byte but rejects the standard NBT Tag::Short variant, so
update the match in this function to handle Tag::Short the same way as the other
numeric cases. Keep the existing conversion to u32 and preserve the current
behavior for the other Tag variants in haya_text/src/chat.rs, using deserialize
as the location to add the missing case.
- Around line 537-557: TextComponent::deserialize currently only accepts
ListTag::Compound for NBT list fields, so valid homogeneous string arrays in
translate.with and extra still fail. Update the Tag::List match in
TextComponent::deserialize, plus the translate.with and extra handling branches,
to also accept ListTag::String and convert each string entry into a literal
TextComponent/Content::Literal sibling instead of returning Err. Keep the
existing compound-list behavior unchanged and use the same deserialization path
where possible so both compound and string list forms are supported.
- Around line 425-428: The match inside the argument serialization path in
TextComponent::serialize currently uses unsafe
core::hint::unreachable_unchecked() for the non-Compound case. Replace that
unsafe fallback with a safe unreachable!() macro, keeping the same match on
arg.serialize() but adding a clear panic message so the invariant is enforced
without introducing UB.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 23dc2b6d-4ad4-4d23-b510-f85f62cce745

📥 Commits

Reviewing files that changed from the base of the PR and between f34f7ec and fe59ba6.

📒 Files selected for processing (2)
  • haya_text/src/chat.rs
  • haya_text/src/decoration.rs

@hayanesuru

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
haya_nbt/src/stringify.rs (1)

557-559: 🩺 Stability & Availability | 🔴 Critical

Critical Safety Issue: Unsafe Slice Bounds in UTF-8 Encoding

The code at line 558 constructs a mutable slice with an invalid length, causing it to extend beyond allocated memory. Specifically, core::slice::from_raw_parts_mut is called with a start pointer at buf.as_mut_ptr().add(buf.len()) but a length of buf.capacity(). The actual available space from the start pointer is only buf.capacity() - buf.len(). When buf.len() > 0, the created slice references memory outside the vector's bounds.

Executing ch.encode_utf8 on this oversized slice results in undefined behavior, as it assumes the entire slice length is valid writable memory.

Apply the proposed fix to limit the slice length to the exact bytes required (ch.len_utf8()) or the available tail capacity. This ensures memory safety by strictly adhering to the allocated buffer boundaries.

Proposed fix
         let ch = escape_quoted(n)?;
         buf.reserve(ch.len_utf8());
+        let len = buf.len();
         let s = ch.encode_utf8(unsafe {
-            core::slice::from_raw_parts_mut(buf.as_mut_ptr().add(buf.len()), buf.capacity())
+            core::slice::from_raw_parts_mut(buf.as_mut_ptr().add(len), ch.len_utf8())
         });
-        unsafe { buf.set_len(buf.len() + s.len()) }
+        unsafe { buf.set_len(len + s.len()) }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_nbt/src/stringify.rs` around lines 557 - 559, The UTF-8 encoding path in
stringify uses an unsafe tail slice with an invalid length, so adjust the
`ch.encode_utf8` call to only expose the remaining writable space from
`buf.as_mut_ptr().add(buf.len())`. Use the exact required byte count
(`ch.len_utf8()`) or the vector’s available tail capacity when building the
mutable slice with `core::slice::from_raw_parts_mut`, and keep the fix localized
to the encoding logic in `stringify.rs` so the slice never extends past the
buffer bounds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@haya_protocol/Cargo.toml`:
- Line 3: The crate version in Cargo.toml is using SemVer build metadata to
encode the protocol revision, which Cargo and crates.io ignore. Update the
version in haya_protocol to a standard SemVer release number by bumping the
patch version for protocol changes, and keep the Minecraft revision in separate
metadata or changelog instead of the version field.

In `@haya_str/src/lib.rs`:
- Around line 148-154: The len-bounded unsafe string conversion is duplicated
between HayaStr::as_mut_str and the AsMut<str> implementation, which risks the
UTF-8 and length invariants drifting apart. Refactor the shared unsafe
construction into one helper on HayaStr and have both as_mut_str and AsMut<str>
delegate to it, keeping the raw slice/from_utf8_unchecked_mut logic in a single
place.

---

Duplicate comments:
In `@haya_nbt/src/stringify.rs`:
- Around line 557-559: The UTF-8 encoding path in stringify uses an unsafe tail
slice with an invalid length, so adjust the `ch.encode_utf8` call to only expose
the remaining writable space from `buf.as_mut_ptr().add(buf.len())`. Use the
exact required byte count (`ch.len_utf8()`) or the vector’s available tail
capacity when building the mutable slice with `core::slice::from_raw_parts_mut`,
and keep the fix localized to the encoding logic in `stringify.rs` so the slice
never extends past the buffer bounds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0bec9d8c-b6ba-488e-82e2-94ef88c609b4

📥 Commits

Reviewing files that changed from the base of the PR and between fe59ba6 and 70d0a9c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • .cargo/config.toml
  • Cargo.toml
  • haya_data/build.rs
  • haya_nbt/src/stringify.rs
  • haya_protocol/Cargo.toml
  • haya_protocol/src/item_stack.rs
  • haya_ser_macro/src/lib.rs
  • haya_str/src/lib.rs
  • haya_text/src/chat.rs

Comment thread haya_protocol/Cargo.toml
Comment thread haya_str/src/lib.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
haya_text/src/chat.rs (2)

806-817: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid accepting atlas objects with a placeholder sprite.

This branch creates ObjectInfo::Atlas with an empty sprite until OBJECT_SPRITE is seen. If the sprite key is absent, deserialization succeeds with a placeholder value that serialize would not intentionally emit. Track atlas/sprite as pending fields and construct ObjectInfo::Atlas only after both required fields are present.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_text/src/chat.rs` around lines 806 - 817, The OBJECT_ATLAS
deserialization path in chat.rs is creating Content::Object/ObjectInfo::Atlas
with a placeholder sprite before OBJECT_SPRITE arrives, so update this branch to
track atlas and sprite as pending values instead of constructing the object
immediately. In the deserialize logic around ObjectInfo::Atlas, only build the
final Atlas variant once both fields are present, and ensure missing sprite data
causes deserialization to fail rather than defaulting to
Identifier::new_const("").unwrap().

619-621: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate discriminator fields instead of discarding them.

TYPE, NBT_SOURCE, and OBJECT_TYPE are emitted by serialize, but deserialize ignores their values. Unsupported or mismatched payloads can be accepted and even fall back to the empty literal default from Lines 611-613. Store these discriminator values and reject unknown or inconsistent combinations after parsing.

Also applies to: 752-754, 803-805

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_text/src/chat.rs` around lines 619 - 621, The discriminator handling in
deserialize is currently discarding the values for TYPE, NBT_SOURCE, and
OBJECT_TYPE, which can let unsupported or mismatched payloads slip through and
fall back to the empty-literal default. Update the parsing logic in the relevant
deserialize match arms to store these fields on the parsed struct, then validate
their combination after parsing and reject unknown, missing, or inconsistent
discriminator values instead of ignoring them; apply the same fix in the other
deserialize sites that mirror this pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@haya_text/src/chat.rs`:
- Around line 806-817: The OBJECT_ATLAS deserialization path in chat.rs is
creating Content::Object/ObjectInfo::Atlas with a placeholder sprite before
OBJECT_SPRITE arrives, so update this branch to track atlas and sprite as
pending values instead of constructing the object immediately. In the
deserialize logic around ObjectInfo::Atlas, only build the final Atlas variant
once both fields are present, and ensure missing sprite data causes
deserialization to fail rather than defaulting to
Identifier::new_const("").unwrap().
- Around line 619-621: The discriminator handling in deserialize is currently
discarding the values for TYPE, NBT_SOURCE, and OBJECT_TYPE, which can let
unsupported or mismatched payloads slip through and fall back to the
empty-literal default. Update the parsing logic in the relevant deserialize
match arms to store these fields on the parsed struct, then validate their
combination after parsing and reject unknown, missing, or inconsistent
discriminator values instead of ignoring them; apply the same fix in the other
deserialize sites that mirror this pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6358e3c1-230e-4fac-a99b-a9c72e7fe97f

📥 Commits

Reviewing files that changed from the base of the PR and between 70d0a9c and 8602b77.

📒 Files selected for processing (2)
  • haya_ident/src/lib.rs
  • haya_text/src/chat.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
haya_data/build.rs (1)

133-144: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Make registry name normalization collision-safe.

name.replace('/', "_") maps both a/b and a_b to a_b, which can generate duplicate type/impl names. Use an injective escape or add a collision assertion before emitting code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_data/build.rs` around lines 133 - 144, The registry name normalization
in the code generation path can collide because name.replace('/', "_") maps
distinct inputs like a/b and a_b to the same identifier. Update the name
normalization used before enum_head, impl_codec, impl_name, and impl_common to
be injective, or add a collision check/assertion in the generation flow so
duplicate generated type/impl names are prevented.
haya_str/src/lib.rs (1)

7-7: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep MAX public or bump the major version — this crate is published, so making haya_str::MAX private breaks downstream imports; either keep the constant public or treat this as a semver-major change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@haya_str/src/lib.rs` at line 7, The `MAX` constant in `haya_str::lib` is
currently private, which breaks downstream users importing `haya_str::MAX`.
Update the visibility of `MAX` to remain public in the crate root, or if you
intentionally want it private, treat the change as a semver-major API break and
adjust the release accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@haya_nbt/src/stringify.rs`:
- Around line 556-561: The character append logic in the string escaping code
uses `from_raw_parts_mut` on Vec spare capacity, which is not safe for
uninitialized bytes. Update the `stringify` path around the `ch.encode_utf8`
call to avoid creating a mutable slice over spare capacity; instead write into a
temporary `[u8; 4]` and append with `extend_from_slice`, or use
`Vec::spare_capacity_mut()` with `MaybeUninit` in the same helper.

In `@haya_text/src/chat.rs`:
- Around line 939-949: The `Content::Object` parsing in `chat.rs` is too
permissive: `atlas` gets ignored when `sprite` is missing, and an
`ObjectInfo::Atlas` can be constructed from malformed payloads that also lack
the proper `object` discriminator. Update the object parsing branch around
`ObjectInfo::Atlas` to validate the field combination first, and return
`Err(Error)` for orphaned `atlas` values or conflicting payloads before
assigning to `content`; use the existing `sprite`, `atlas`, and
`Content::Object`/`ObjectInfo::Atlas` symbols to keep the fix local.

---

Outside diff comments:
In `@haya_data/build.rs`:
- Around line 133-144: The registry name normalization in the code generation
path can collide because name.replace('/', "_") maps distinct inputs like a/b
and a_b to the same identifier. Update the name normalization used before
enum_head, impl_codec, impl_name, and impl_common to be injective, or add a
collision check/assertion in the generation flow so duplicate generated
type/impl names are prevented.

In `@haya_str/src/lib.rs`:
- Line 7: The `MAX` constant in `haya_str::lib` is currently private, which
breaks downstream users importing `haya_str::MAX`. Update the visibility of
`MAX` to remain public in the crate root, or if you intentionally want it
private, treat the change as a semver-major API break and adjust the release
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2222d055-d774-4d58-bdb0-5bf1658e5a7c

📥 Commits

Reviewing files that changed from the base of the PR and between 8602b77 and a73698a.

📒 Files selected for processing (4)
  • haya_data/build.rs
  • haya_nbt/src/stringify.rs
  • haya_str/src/lib.rs
  • haya_text/src/chat.rs

Comment thread haya_nbt/src/stringify.rs Outdated
Comment thread haya_text/src/chat.rs
@hayanesuru

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hayanesuru
hayanesuru merged commit 63348c0 into main Jun 30, 2026
2 checks passed
@hayanesuru
hayanesuru deleted the text branch June 30, 2026 02:55
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.

1 participant