Skip to content

feat: add sortContacts action - #1

Open
BTForIT wants to merge 17 commits into
mainfrom
feat/filterContacts-sort
Open

feat: add sortContacts action#1
BTForIT wants to merge 17 commits into
mainfrom
feat/filterContacts-sort

Conversation

@BTForIT

@BTForIT BTForIT commented Apr 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a separate `sortContacts(contacts, sortBy, orderBy)` cherri action that compiles to `filter.contacts` with sort settings only (no filter templates)
  • Enables sort+filter pipelines for contacts without needing post-process plist edits

Why a separate action (not new params on filterContacts)?

`filterContacts` has `values` declared as `infinite: true` — additional positional params after it would be ambiguous. A separate `sortContacts` action is cleaner and allows natural chaining:

```cherri
const Sorted = sortContacts(AllContacts, "Last Name", "A to Z")
const Filtered = filterContacts(Sorted, "Job Title", "contains", "{tag}", "fallback")
```

Test plan

  • Build cherri locally and compile a test .cherri using `sortContacts`
  • Verify compiled plist has `WFContentItemSortProperty` + `WFContentItemSortOrder` and no filter templates on the sort action
  • Verify a chained `filterContacts` properly references the sorted action's output
  • Deploy to a real Shortcut on Mac Mini, run via CloudKit-synced iPhone — sort order preserved through filters

🤖 Generated with Claude Code

BTForIT and others added 17 commits March 23, 2026 10:31
Moves filterContacts from actions/contacts.cherri to actions_std.go to
enable WFContentItemFilter predicate generation. This addresses electrikmilk#128 by
adding support for filter conditions on contact properties.

New syntax:
  filterContacts(contacts, "Job Title", "contains", "#All")
  filterContacts(contacts, "Job Title", "contains", "#{DeviceText}", "#All")

The implementation:
- Accepts filterProperty, filterOperator, and variadic filterValues
- Generates WFContentPredicateTableTemplate with proper predicates
- Supports inline variable references via WFTextTokenString
- Uses OR logic (WFActionParameterFilterPrefix=0) for multiple values
- Maps operator strings to Shortcuts operator codes:
  is=4, is not=5, contains=99, does not contain=999,
  begins with=8, ends with=9
- Includes decompilation support

Fixes electrikmilk#128
Adds compiler warnings when:
- An action output gets a generic name like "Text" or "Text 1"
  instead of a descriptive const name

This catches a common issue where @variable = "string" creates
unnamed gettext actions that show as broken references in the
Shortcuts editor. Using const instead produces named outputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changes unnamed text block detection from warning to compiler error.
Using @variable = "string" now fails compilation with:
  Error: Unnamed text block 'Text'. Use 'const descriptiveName = ...'

This prevents deploying shortcuts with broken/unnamed references
that show as red text on iOS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notes was missing from the contactDetail enum, preventing
getContactDetail(contact, "Notes") from compiling. This is
needed for vCard-based launchers that use the NOTE field for
routing targets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Catches two cases:
1. Conditional with no comparison value at all (missing WFConditionalActionString)
2. Conditional with empty string "" as comparison value

Both cases cause shortcuts to show broken conditions on iOS.
Conditions 100/101 (has value/has no value) are exempt since
they don't use comparison strings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds reference comment documenting the non-obvious WFCondition code
mapping (2=greater than, NOT contains; 99=contains; 4=is, NOT 0).

Also validates that comparison conditionals have non-empty values
at compile time — catches blank WFConditionalActionString before
it reaches the phone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kmilk#149)

The rawAction check function set overrideIdentifier on the shared
actions["rawAction"] definition. Since all rawAction calls share
this definition, each call overwrote the previous one's identifier,
causing every rawAction to compile with the LAST call's identifier.

Fix: remove the shared mutation from check(), and instead set
currentAction.overrideIdentifier per-call during the generation
phase (shortcutgen.go), right after setCurrentAction copies the
definition. This scopes the identifier to each individual call.

Before: rawAction("A", {}); rawAction("B", {}) → both compile as "B"
After:  rawAction("A", {}); rawAction("B", {}) → "A" and "B" respectively

Fixes electrikmilk#149

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ixes

- Remove filterContacts comment in contacts.cherri
- Remove explanatory comments in actions_std.go make/decomp functions
- Rename filterProperty->property, filterOperator->operator, filterValues->values
- Enum renamed from filterOperator to operator in action.go

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The toolkit DB (Tools-active) overrides .cherri-defined enum values
when both exist. Some valid Shortcuts values (e.g. "Notes" for
contactDetail) are defined in .cherri but missing from the DB.

Previously, defineParamEnums would skip updating if the enum name
already existed, effectively letting the DB values win. Now it
merges both sources — DB values plus any additional values from the
.cherri action definitions — so nothing gets silently dropped.

This fixes getContactDetail(contact, "Notes") failing with
"Invalid value 'Notes'" despite Notes being a valid
WFContentItemPropertyName in Apple Shortcuts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…context

Two bugs in rawAction handling:

1. actions_std.go: getArgValue(args[1]).(map[string]interface{}) panics
   when the dict is empty ({}). Empty dict literal is parsed as nil,
   causing type assertion to fail. Now checks for nil and returns
   empty map instead.

2. shortcutgen.go: When rawAction is used in an expression context
   (e.g. const X = rawAction(...)), the overrideIdentifier was never
   set, causing the action to emit with identifier
   'is.workflow.actions.rawaction' instead of the user-provided one.
   The fix replicates the override logic in the Variable branch of
   makeVariableValue, matching what generateActions already does for
   standalone rawAction statements.

Together these enable: const X = rawAction("com.apple.Foo", {})

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a separate `sortContacts(contacts, sortBy, orderBy)` action that
compiles to filter.contacts with sort settings only (no filter templates).

This works around the inability to add positional sort/orderBy params to
the existing filterContacts action — its `values` parameter has
infinite:true, so additional positional params would conflict.

Chains naturally: sortContacts(All, "Last Name", "A to Z") → filterContacts
with the sort preserved through filter operations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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