Skip to content

feat: complete card message format#1198

Open
91-enjoy wants to merge 1 commit into
larksuite:mainfrom
91-enjoy:feat/complete_card_message_format
Open

feat: complete card message format#1198
91-enjoy wants to merge 1 commit into
larksuite:mainfrom
91-enjoy:feat/complete_card_message_format

Conversation

@91-enjoy
Copy link
Copy Markdown
Contributor

@91-enjoy 91-enjoy commented Jun 1, 2026

Change-Id: I422474ab6b7505e48ab5697793900df035be6e29

Summary

The card message converter (shortcuts/im/convert_lib/card.go) previously
rendered a subset of card fields and had several mode-gated behaviors that
caused information to be silently dropped in concise mode. This PR audits
every element handler and brings the output up to full fidelity:
missing header fields are rendered, collapsible panels always expand, rich
element metadata (images, audio, video, overflow URLs, person names) is no
longer hidden behind cardModeDetailed, and several format bugs are fixed.

Changes

Header

  • json_attachment object supportjson_attachment at the top level can
    be either a JSON string (existing path) or a plain object; both are now
    handled, unblocking person-name resolution in this attachment path.
  • Subtitle — new extractHeaderSubtitle extracts header.property.subtitle
    (or header.subtitle) and emits it as a subtitle="…" attribute on the
    <card> tag.
  • Header tags — new extractHeaderTags extracts textTagList from
    header.property and appends the rendered tags on the line after <card>.

Element fixes

  • div notation detection — was reading stale top-level text_size; now
    reads textStyle.size via extractProperty, matching current API schema.
  • local_datetime float millisecondsmilliseconds field can be a JSON
    number (float64) or string; added type switch so numeric timestamps are no
    longer silently dropped.
  • Collapsible panel always renders content — removed the shouldExpand
    gate that skipped content in concise mode; content is always rendered with
    / indicator controlled by the expanded flag alone.
  • Button improvements:
    • Disabled state + disabledTips rendered in all modes (was concise-only).
    • URL action extraction decoupled from disabled check.
    • New confirm dialog rendered as (confirm:"title: text") annotation.
  • Overflow menu — options now render as Markdown links when they carry
    open_url actions; otherwise append (value) for traceability.
  • Select multi attribute(multi) annotation was gated behind detailed
    mode; now always emitted. (type:person) remains detailed-only.
  • Select option user lookup — when an option has no display text, fall back
    to lookupOptionUserName (resolving from option_users attachment) before
    using raw value.
  • Image select value & imageID — image options now show (value) and
    (img_key:…) / (img_token:…) resolved from attachment.

Metadata now shown in all modes (removed cardModeDetailed gates)

  • convertImage — image key/token always appended.
  • convertImgCombination — image keys always appended.
  • convertAudio / convertVideo — file key always appended.
  • Image resolution upgraded: getImageTokengetImageKeyAndToken returns
    both origin_key and token; origin_key takes priority (shown as
    img_key:).

Person rendering

  • Removed @ prefix from convertPerson, convertPersonV1,
    convertPersonList, convertAvatar — the prefix implied an @-mention but
    these are data field values.
  • convertPersonList now uses lookupPersonName to resolve display names from
    attachment, with name(open_id:…) format in detailed mode.
  • convertAvatar now resolves and displays the person name when available.
  • New lookupOptionUserName helper reads from attachment.option_users.

Chart

  • Title format: chart type now appended as (Bar chart) suffix rather than
    direct concatenation, preventing "SalesBar chart" style output.
  • VChart data spec support: data can be an array of series objects
    [{"id":"…","values":[…]}] in addition to the legacy {"values":[…]} map.

Table

  • Float cell values: changed FormatFloat(v, 'f', 2, 64) to 'f', -1, 64
    to avoid spurious .00 suffix on whole numbers.
  • New goMapArrayTexts helper: parses Go-format slice-of-maps strings such as
    [map[text:VIP] map[text:Premium]] (produced by Lark API for array-type
    cells) into comma-joined values VIP, Premium.

Test Plan

  • make unit-test passed (go test -race ./shortcuts/im/...)
  • New / updated table-driven cases in card_test.go:
    cells) into comma-joined values VIP, Premium.

Test Plan

  • make unit-test passed (go test -race ./shortcuts/im/...)
  • New / updated table-driven cases in card_test.go:
    • json_attachment as object, person names resolved (C008)
    • extractHeaderSubtitle with property.subtitle (C002)
    • extractHeaderTags with textTagList (C003)
    • convertDiv notation via textStyle.size (updated existing case)
    • convertCollapsiblePanel concise+collapsed still renders content (C001)
    • convertButton disabled + disabledTips in all modes (C007)
    • convertSelect person with no option text resolved from option_users
    • convertSelectImg with value + imageIDimg_key
    • convertImage prefers origin_key over token
    • convertImgCombination with origin_key
    • convertChart title format (Bar chart)
    • extractTableCellValue Go-format array string → VIP, Premium
    • convertTable float cell without trailing zeros
    • convertPerson / convertPersonV1 / convertPersonList / convertAvatar
      without @ prefix, with name lookup
    • Dispatch test for person element updated

Related Issues

N/A

Label: feature

Summary by CodeRabbit

  • Bug Fixes

    • Fixed person mention formatting and user resolution
    • Improved image key and token handling for proper display
    • Enhanced table cell value extraction and float formatting
  • Improvements

    • Better extraction and rendering of card header metadata (subtitles, tags)
    • Improved rendering consistency across card elements
    • Enhanced attachment and user data integration
    • More consistent button state and formatting representation

Change-Id: I422474ab6b7505e48ab5697793900df035be6e29
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The card converter library now extracts and renders header metadata (subtitle and tags), expands attachment handling to accept decoded JSON objects, enhances element display formatting across multiple converters, introduces attachment-based fallback lookups for person and image rendering, and systematically updates output formats from @-mention style to explicit metadata wrappers.

Changes

Card Converter Element and Metadata Enhancements

Layer / File(s) Summary
Header metadata extraction and card wrapper generation
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
Header subtitle and tag extraction are performed during conversion, and the card wrapper template conditionally includes title, subtitle, and headerTags in the output.
Attachment handling expansion and lookup helpers
shortcuts/im/convert_lib/card.go
Attachment handling now accepts json_attachment as a decoded object, and new helper functions provide attachment-based lookups for option user names and image metadata (origin_key/token pairs).
Element display converter updates (div, datetime, collapsible, button)
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
div notation style detection now reads textStyle.size; local_datetime parses milliseconds as string or numeric; collapsible_panel always renders expansion indicators; button rendering handles disabled state and appends disabled tips consistently.
Select and overflow option text resolution with fallbacks
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
Option text resolution now falls back to attachment-based user name lookup and optionally renders with image metadata; overflow options support markdown link formatting and value suffixes.
Image and img_combination metadata rendering
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
Image converters now append origin_key or token metadata; img_combination accumulates and formats image keys across the image list.
Chart, audio/video, and table output updates
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
Chart titles are conditionally replaced or parenthesized by type; chart summary extraction supports multiple data encodings (maps with values or VChart series arrays); audio/video unconditionally append (key:...) metadata; table cells support Go-format map-slice parsing and variable-precision float formatting.
Person and avatar output formatting changes
shortcuts/im/convert_lib/card.go, shortcuts/im/convert_lib/card_test.go
Person, person_v1, person_list, and avatar outputs change from @-mention style to explicit open_id/id wrappers; person lookup now uses attachment-provided user names with fallback to raw user IDs.
Test fixtures and expectations for all converter changes
shortcuts/im/convert_lib/card_test.go
Test fixtures are updated to include attachment token/origin_key pairs; test expectations are updated to match new output formats (img_key, open_id wrappers, chart type suffixes, Go-format table parsing, and person name resolution); new test cases cover json_attachment objects, concise-mode collapsible panels, and disabled button tips.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Cards now wear their metadata crowns,
With headers adorned in subtitle gowns,
Attachments unlock person names from the deep,
And images share their origin secrets to keep,
From @ to open_id, the formatting dance,
Each element converter takes its sweet chance!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: complete card message format' directly captures the main objective of the PR—completing/auditing card message format handling across all element converters.
Description check ✅ Passed The description follows the required template with clear Summary, Changes, and Test Plan sections; includes detailed subsections documenting header, element fixes, metadata changes, person rendering, chart, and table updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@github-actions github-actions Bot added domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths labels Jun 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
shortcuts/im/convert_lib/card.go (1)

344-347: ⚡ Quick win

Use the element tag, not id, to decide type:person.

type:person is currently emitted only when the element id happens to contain "person". A normal select_person like id: "assignee" will render without the person annotation in detailed mode.

♻️ Suggested fix
-case "select_static", "select_person":
-	return c.convertSelect(prop, id, false)
-case "multi_select_static", "multi_select_person":
-	return c.convertSelect(prop, id, true)
+case "select_static":
+	return c.convertSelect(prop, id, false, false)
+case "select_person":
+	return c.convertSelect(prop, id, false, true)
+case "multi_select_static":
+	return c.convertSelect(prop, id, true, false)
+case "multi_select_person":
+	return c.convertSelect(prop, id, true, true)
-func (c *cardConverter) convertSelect(prop cardObj, id string, isMulti bool) string {
+func (c *cardConverter) convertSelect(prop cardObj, id string, isMulti, isPerson bool) string {
...
-	if c.mode == cardModeDetailed && strings.Contains(id, "person") {
+	if c.mode == cardModeDetailed && isPerson {
		attrs = append(attrs, "type:person")
	}

Also applies to: 998-1076

🤖 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 `@shortcuts/im/convert_lib/card.go` around lines 344 - 347, The code is
determining type:person based on the element id instead of the element tag;
update the select conversion to use the element tag to decide person vs static.
In function convertSelect (and all call sites such as the case branches for
"select_static"/"select_person" and "multi_select_static"/"multi_select_person")
change the boolean/person-detection logic to inspect prop.Tag (or the element
tag field on the passed property) for the substring "person" (or exact match)
rather than checking id, and propagate that boolean into convertSelect so it
emits type:person correctly; apply the same change to the other similar blocks
referenced in the review (around the 998-1076 region).
🤖 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 `@shortcuts/im/convert_lib/card.go`:
- Around line 889-897: The early return inside the disabled branch causes
disabled buttons to skip URL/confirm rendering; modify the disabled handling so
it marks the label (using buttonText and prop["disabled"]/prop["disabledTips"]
and c.extractTextContent) but does not return early — instead set the result
prefix (e.g., "[%s ✗]") and allow the function to continue to the existing
URL/confirm append logic (the code handling URL/confirm rendering later in the
function) so disabled buttons retain URL and confirm metadata.
- Around line 1457-1487: The function goMapArrayTexts incorrectly stops values
at the first space because it uses strings.IndexAny(after, " ]"); change the
parsing to detect the closing bracket instead (e.g. use strings.IndexByte(after,
']') or similar) so multi-word values like "Premium VIP" are captured intact,
then trim surrounding whitespace/brackets from val before appending; update the
logic that sets end/val/rest (refer to variables after, end, val and the key
constant) to use the ']' delimiter rather than breaking on space.

---

Nitpick comments:
In `@shortcuts/im/convert_lib/card.go`:
- Around line 344-347: The code is determining type:person based on the element
id instead of the element tag; update the select conversion to use the element
tag to decide person vs static. In function convertSelect (and all call sites
such as the case branches for "select_static"/"select_person" and
"multi_select_static"/"multi_select_person") change the boolean/person-detection
logic to inspect prop.Tag (or the element tag field on the passed property) for
the substring "person" (or exact match) rather than checking id, and propagate
that boolean into convertSelect so it emits type:person correctly; apply the
same change to the other similar blocks referenced in the review (around the
998-1076 region).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76071ed3-36ba-4638-9dff-d46c3b10d65c

📥 Commits

Reviewing files that changed from the base of the PR and between 99e314f and c08ef18.

📒 Files selected for processing (2)
  • shortcuts/im/convert_lib/card.go
  • shortcuts/im/convert_lib/card_test.go

Comment on lines 889 to +897
disabled, _ := prop["disabled"].(bool)
if disabled && c.mode == cardModeConcise {
return fmt.Sprintf("[%s ✗]", buttonText)
if disabled {
result := fmt.Sprintf("[%s ✗]", buttonText)
if tips, ok := prop["disabledTips"].(cardObj); ok {
if tipsText := c.extractTextContent(tips); tipsText != "" {
result += fmt.Sprintf("(tips:\"%s\")", tipsText)
}
}
return result
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep URL and confirm metadata on disabled buttons.

The early return on Line 890 means disabled buttons never reach the URL/confirm rendering below, so cards with disabled=true still lose those details entirely.

Also applies to: 900-935

🤖 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 `@shortcuts/im/convert_lib/card.go` around lines 889 - 897, The early return
inside the disabled branch causes disabled buttons to skip URL/confirm
rendering; modify the disabled handling so it marks the label (using buttonText
and prop["disabled"]/prop["disabledTips"] and c.extractTextContent) but does not
return early — instead set the result prefix (e.g., "[%s ✗]") and allow the
function to continue to the existing URL/confirm append logic (the code handling
URL/confirm rendering later in the function) so disabled buttons retain URL and
confirm metadata.

Comment on lines +1457 to +1487
// goMapArrayTexts extracts "text" values from a Go-format slice-of-maps string,
// e.g. "[map[text:VIP] map[text:Premium]]" → ["VIP", "Premium"].
// Returns nil if the string doesn't look like this format.
func goMapArrayTexts(s string) []string {
if !strings.HasPrefix(s, "[") || !strings.Contains(s, "map[") {
return nil
}
const key = "text:"
var texts []string
rest := s
for {
idx := strings.Index(rest, key)
if idx < 0 {
break
}
after := rest[idx+len(key):]
end := strings.IndexAny(after, " ]")
var val string
if end < 0 {
val = after
rest = ""
} else {
val = after[:end]
rest = after[end:]
}
if val != "" {
texts = append(texts, val)
}
}
return texts
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

goMapArrayTexts truncates multi-word values.

strings.IndexAny(after, " ]") stops at the first space, so a cell like [map[text:Premium VIP]] becomes Premium. That will corrupt common labels and names in table output.

🤖 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 `@shortcuts/im/convert_lib/card.go` around lines 1457 - 1487, The function
goMapArrayTexts incorrectly stops values at the first space because it uses
strings.IndexAny(after, " ]"); change the parsing to detect the closing bracket
instead (e.g. use strings.IndexByte(after, ']') or similar) so multi-word values
like "Premium VIP" are captured intact, then trim surrounding
whitespace/brackets from val before appending; update the logic that sets
end/val/rest (refer to variables after, end, val and the key constant) to use
the ']' delimiter rather than breaking on space.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 63.36207% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.11%. Comparing base (6d1f998) to head (c08ef18).
⚠️ Report is 58 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/im/convert_lib/card.go 63.36% 57 Missing and 28 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1198      +/-   ##
==========================================
+ Coverage   67.80%   69.11%   +1.30%     
==========================================
  Files         591      630      +39     
  Lines       55237    59444    +4207     
==========================================
+ Hits        37454    41083    +3629     
- Misses      14675    15030     +355     
- Partials     3108     3331     +223     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@c08ef187ba459741ee4ec56939ba633b6dd9a10e

🧩 Skill update

npx skills add 91-enjoy/cli#feat/complete_card_message_format -y -g

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

Labels

domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant