hap-cli 0.9.1__tar.gz → 0.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. {hap_cli-0.9.1 → hap_cli-0.9.3}/PKG-INFO +1 -1
  2. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/smoke.py +292 -0
  3. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/__init__.py +1 -1
  4. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/worksheet_cmd.py +1 -1
  5. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/context.py +5 -0
  6. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/ops.py +119 -3
  7. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/reader.py +137 -1
  8. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/field_spec_adapter.py +6 -0
  9. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/record.py +6 -2
  10. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/record_form_checks.py +30 -4
  11. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/worksheet.py +288 -49
  12. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_core.py +171 -0
  13. hap_cli-0.9.3/hap_cli/tests/test_create_worksheet_partial.py +231 -0
  14. hap_cli-0.9.3/hap_cli/tests/test_editor_inline_subtable.py +239 -0
  15. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g6_advanced_fields.py +5 -0
  16. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_form_checks.py +126 -0
  17. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/PKG-INFO +1 -1
  18. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/SOURCES.txt +2 -0
  19. {hap_cli-0.9.1 → hap_cli-0.9.3}/MANIFEST.in +0 -0
  20. {hap_cli-0.9.1 → hap_cli-0.9.3}/README.md +0 -0
  21. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/__init__.py +0 -0
  22. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/__main__.py +0 -0
  23. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/config.py +0 -0
  24. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/fixtures/__init__.py +0 -0
  25. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/fixtures/feedback_scenario.py +0 -0
  26. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/harness.py +0 -0
  27. {hap_cli-0.9.1 → hap_cli-0.9.3}/app_live_tests/state.py +0 -0
  28. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/README.md +0 -0
  29. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/README_CN.md +0 -0
  30. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/__main__.py +0 -0
  31. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/_entry.py +0 -0
  32. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/__init__.py +0 -0
  33. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/app_cmd.py +0 -0
  34. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/app_creator_cmd.py +0 -0
  35. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/app_editor_cmd.py +0 -0
  36. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/attachment_cmd.py +0 -0
  37. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/auth_cmd.py +0 -0
  38. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/calendar_cmd.py +0 -0
  39. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/chart_cmd.py +0 -0
  40. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/chat_cmd.py +0 -0
  41. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/chatbot_cmd.py +0 -0
  42. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/config_cmd.py +0 -0
  43. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/contact_cmd.py +0 -0
  44. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/department_cmd.py +0 -0
  45. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/group_cmd.py +0 -0
  46. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/guide_cmd.py +0 -0
  47. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/icon_cmd.py +0 -0
  48. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/instance_cmd.py +0 -0
  49. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/node_cmd.py +0 -0
  50. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/optionset_cmd.py +0 -0
  51. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/page_cmd.py +0 -0
  52. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/post_cmd.py +0 -0
  53. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/record_cmd.py +0 -0
  54. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/region_cmd.py +0 -0
  55. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/role_cmd.py +0 -0
  56. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/update_cmd.py +0 -0
  57. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/upload_cmd.py +0 -0
  58. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/v3_registry.py +0 -0
  59. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/commands/workflow_cmd.py +0 -0
  60. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/__init__.py +0 -0
  61. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/action_spec_adapter.py +0 -0
  62. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app.py +0 -0
  63. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/__init__.py +0 -0
  64. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  65. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  66. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  67. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/charts.py +0 -0
  68. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/cleanup.py +0 -0
  69. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/compiler.py +0 -0
  70. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/config.py +0 -0
  71. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  72. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/errors.py +0 -0
  73. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  74. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/executor.py +0 -0
  75. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/fields.py +0 -0
  76. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/hap.py +0 -0
  77. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/merge.py +0 -0
  78. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  79. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/console.py +0 -0
  80. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  81. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  82. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/report.py +0 -0
  83. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/schema.py +0 -0
  84. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  85. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  86. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/cli.py +0 -0
  87. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/executor.py +0 -0
  88. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  89. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  90. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  91. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  92. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/template.py +0 -0
  93. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/selftest.py +0 -0
  94. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/smoke.py +0 -0
  95. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/steps.py +0 -0
  96. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/store.py +0 -0
  97. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/validate.py +0 -0
  98. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  99. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/attachment.py +0 -0
  100. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/auth.py +0 -0
  101. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/calendar_mod.py +0 -0
  102. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/chart.py +0 -0
  103. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/chat.py +0 -0
  104. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/chatbot.py +0 -0
  105. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/cli_access.py +0 -0
  106. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/condition_dsl.py +0 -0
  107. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/contact.py +0 -0
  108. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/control_type_codes.py +0 -0
  109. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/department.py +0 -0
  110. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/__init__.py +0 -0
  111. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  112. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  113. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/apply.py +0 -0
  114. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/componentlower.py +0 -0
  115. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  116. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  117. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  118. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  119. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/editspec.py +0 -0
  120. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/errors.py +0 -0
  121. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/fieldlower.py +0 -0
  122. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  123. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/models.py +0 -0
  124. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/editor/planner.py +0 -0
  125. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/field_normalizer.py +0 -0
  126. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/filter_translator.py +0 -0
  127. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/filter_v3.py +0 -0
  128. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/flow_node.py +0 -0
  129. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/global_meta.py +0 -0
  130. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/group.py +0 -0
  131. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/guide.py +0 -0
  132. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/icon_index.py +0 -0
  133. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/instance.py +0 -0
  134. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/layout_check.py +0 -0
  135. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/logger.py +0 -0
  136. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/optionset.py +0 -0
  137. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/page.py +0 -0
  138. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/post.py +0 -0
  139. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/record_defaults.py +0 -0
  140. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/record_lookups.py +0 -0
  141. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/record_rules.py +0 -0
  142. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/relation_scope.py +0 -0
  143. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/response_crypto.py +0 -0
  144. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/role.py +0 -0
  145. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/role_perm_builder.py +0 -0
  146. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/rule_spec_adapter.py +0 -0
  147. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/session.py +0 -0
  148. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/token_crypto.py +0 -0
  149. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/update.py +0 -0
  150. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/upload.py +0 -0
  151. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/v3/__init__.py +0 -0
  152. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/v3/dispatcher.py +0 -0
  153. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/v3/render.py +0 -0
  154. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/v3/schema.py +0 -0
  155. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/view_spec_adapter.py +0 -0
  156. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/workflow.py +0 -0
  157. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/workflow_node_dsl.py +0 -0
  158. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/worksheet_form.py +0 -0
  159. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/worksheet_reference.py +0 -0
  160. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/core/worksheet_templates.py +0 -0
  161. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/approval.md +0 -0
  162. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/chart.md +0 -0
  163. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/messages.md +0 -0
  164. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/overview.md +0 -0
  165. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/post.md +0 -0
  166. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/record.md +0 -0
  167. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/setup.md +0 -0
  168. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/skills.md +0 -0
  169. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/workflow.md +0 -0
  170. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/en/worksheet.md +0 -0
  171. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/index.json +0 -0
  172. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/approval.md +0 -0
  173. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/chart.md +0 -0
  174. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/messages.md +0 -0
  175. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/overview.md +0 -0
  176. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/post.md +0 -0
  177. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/record.md +0 -0
  178. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/setup.md +0 -0
  179. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/skills.md +0 -0
  180. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/workflow.md +0 -0
  181. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
  182. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/i18n.py +0 -0
  183. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/locale/__init__.py +0 -0
  184. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/locale/messages.json +0 -0
  185. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/locale/messages.schema.json +0 -0
  186. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/__init__.py +0 -0
  187. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/conftest.py +0 -0
  188. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
  189. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
  190. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_app_creator.py +0 -0
  191. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_app_editor.py +0 -0
  192. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_app_export_usage.py +0 -0
  193. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_app_light_color.py +0 -0
  194. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_app_section_name.py +0 -0
  195. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
  196. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_approval_wire.py +0 -0
  197. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_atomic_write.py +0 -0
  198. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_attachment_share.py +0 -0
  199. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_auth_prerelease.py +0 -0
  200. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_batch_add_atomic.py +0 -0
  201. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_bug_036430.py +0 -0
  202. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_bug_036750.py +0 -0
  203. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_calendar_date_range.py +0 -0
  204. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
  205. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chart.py +0 -0
  206. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chart_filter_merge.py +0 -0
  207. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  208. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chat_messages_cli.py +0 -0
  209. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chat_recall_cli.py +0 -0
  210. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_chat_send_socket.py +0 -0
  211. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_cli_access.py +0 -0
  212. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_coded_responses.py +0 -0
  213. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl.py +0 -0
  214. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
  215. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
  216. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_config_cmd.py +0 -0
  217. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_content_language.py +0 -0
  218. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  219. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  220. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
  221. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
  222. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_engine_core_parity.py +0 -0
  223. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
  224. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_bidirectional.py +0 -0
  225. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
  226. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_entry_module.py +0 -0
  227. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
  228. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_http_error_body.py +0 -0
  229. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
  230. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
  231. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_layout_check.py +0 -0
  232. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
  233. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_node_summary.py +0 -0
  234. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_post_cursor.py +0 -0
  235. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
  236. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
  237. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
  238. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_subgroups.py +0 -0
  239. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_subtable_read.py +0 -0
  240. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
  241. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
  242. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
  243. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
  244. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_full_e2e.py +0 -0
  245. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g1_chart_parity.py +0 -0
  246. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g1_record_parity.py +0 -0
  247. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g1_upload_parity.py +0 -0
  248. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
  249. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
  250. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
  251. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g4_approval_parity.py +0 -0
  252. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
  253. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
  254. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
  255. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
  256. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g8_filter_operator.py +0 -0
  257. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
  258. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
  259. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
  260. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_global_meta.py +0 -0
  261. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_guide.py +0 -0
  262. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_guide_sections.py +0 -0
  263. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_i18n.py +0 -0
  264. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
  265. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_instance_envelope.py +0 -0
  266. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_instance_history_cli.py +0 -0
  267. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration.py +0 -0
  268. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_approval.py +0 -0
  269. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  270. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_calendar.py +0 -0
  271. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_destructive.py +0 -0
  272. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_misc.py +0 -0
  273. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_post.py +0 -0
  274. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_social.py +0 -0
  275. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_v3.py +0 -0
  276. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_workflow.py +0 -0
  277. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  278. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
  279. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  280. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_multi_profile.py +0 -0
  281. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  282. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
  283. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  284. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_org_id_cli.py +0 -0
  285. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_org_id_docs.py +0 -0
  286. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_page_filters_group_read.py +0 -0
  287. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_page_filters_group_update.py +0 -0
  288. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_page_save_page_id.py +0 -0
  289. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_parameter_conventions.py +0 -0
  290. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  291. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_post_scope_cli.py +0 -0
  292. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  293. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_business_rules.py +0 -0
  294. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_defaults.py +0 -0
  295. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
  296. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_field_alias.py +0 -0
  297. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_run_action.py +0 -0
  298. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  299. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_record_write_fixes.py +0 -0
  300. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_relation_scope.py +0 -0
  301. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
  302. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_role_app_scope.py +0 -0
  303. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
  304. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
  305. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_save_rule_spec.py +0 -0
  306. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_select_add_option.py +0 -0
  307. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  308. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  309. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
  310. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_update.py +0 -0
  311. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_update_mode.py +0 -0
  312. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_upload_allow_down.py +0 -0
  313. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  314. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
  315. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  316. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_bool_flags.py +0 -0
  317. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  318. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  319. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_response_format_json.py +0 -0
  320. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_session.py +0 -0
  321. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  322. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
  323. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_config_keys.py +0 -0
  324. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_inner_scope.py +0 -0
  325. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
  326. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
  327. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
  328. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_structure_listing.py +0 -0
  329. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
  330. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
  331. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_wo4_result_handling.py +0 -0
  332. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_code_node.py +0 -0
  333. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
  334. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
  335. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
  336. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  337. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
  338. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  339. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_node_wire.py +0 -0
  340. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
  341. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  342. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  343. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  344. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  345. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
  346. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_switch.py +0 -0
  347. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  348. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/__init__.py +0 -0
  349. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/atomic_write.py +0 -0
  350. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/confirm.py +0 -0
  351. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/formatting.py +0 -0
  352. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/options.py +0 -0
  353. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli/utils/parameter_mapping.py +0 -0
  354. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/dependency_links.txt +0 -0
  355. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/entry_points.txt +0 -0
  356. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/requires.txt +0 -0
  357. {hap_cli-0.9.1 → hap_cli-0.9.3}/hap_cli.egg-info/top_level.txt +0 -0
  358. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/__init__.py +0 -0
  359. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/__main__.py +0 -0
  360. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/_wftest.py +0 -0
  361. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/charts.py +0 -0
  362. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/cleanup.py +0 -0
  363. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/compiler.py +0 -0
  364. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/config.py +0 -0
  365. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/errors.py +0 -0
  366. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/executor.py +0 -0
  367. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/fields.py +0 -0
  368. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/hap.py +0 -0
  369. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/recording/__init__.py +0 -0
  370. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/recording/console.py +0 -0
  371. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/recording/jsonl.py +0 -0
  372. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/recording/mirror.py +0 -0
  373. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/recording/report.py +0 -0
  374. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/schema.py +0 -0
  375. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/seed/__init__.py +0 -0
  376. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/seed/cli.py +0 -0
  377. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/seed/executor.py +0 -0
  378. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/seed/template.py +0 -0
  379. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/selftest.py +0 -0
  380. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/smoke.py +0 -0
  381. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/step.py +0 -0
  382. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/steps.py +0 -0
  383. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/store.py +0 -0
  384. {hap_cli-0.9.1 → hap_cli-0.9.3}/live_tests/workflow_dsl.py +0 -0
  385. {hap_cli-0.9.1 → hap_cli-0.9.3}/setup.cfg +0 -0
  386. {hap_cli-0.9.1 → hap_cli-0.9.3}/setup.py +0 -0
  387. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/INDEX.json +0 -0
  388. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  389. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  390. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/create_optionset.yaml +0 -0
  391. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/create_role.yaml +0 -0
  392. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  393. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/delete_record.yaml +0 -0
  394. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/delete_role.yaml +0 -0
  395. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  396. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_info.yaml +0 -0
  397. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  398. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  399. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  400. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_details.yaml +0 -0
  401. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  402. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_list.yaml +0 -0
  403. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  404. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  405. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  406. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  407. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_regions.yaml +0 -0
  408. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_role_details.yaml +0 -0
  409. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/get_role_list.yaml +0 -0
  410. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  411. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  412. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  413. {hap_cli-0.9.1 → hap_cli-0.9.3}/sources/v3-api-schema/update_optionset.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -226,6 +226,146 @@ def case_custom_action(app_id: str, section_id: str) -> None:
226
226
  hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
227
227
 
228
228
 
229
+ def case_custom_action_update(app_id: str, section_id: str) -> None:
230
+ """bug #455751: --action-spec with --btn-id edits the action in place."""
231
+ print("scenario: custom-action update by id")
232
+ ws_id = _make_worksheet(app_id, section_id, "LT-action-update")
233
+ try:
234
+ ok, res = _try(["worksheet", "create-custom-action", ws_id, "-a",
235
+ app_id, "--action-spec", json.dumps(
236
+ {"name": "ORIGINAL", "type": "triggerWorkflow",
237
+ "color": "#2196F3"})])
238
+ check("update: workflow action created", ok, str(res)[:160])
239
+ btn_id = res.get("actionId") if ok else ""
240
+ proc_id = res.get("processId") if ok else ""
241
+ ok, res = _try(["worksheet", "create-custom-action", ws_id, "-a",
242
+ app_id, "--btn-id", btn_id, "--action-spec",
243
+ json.dumps({"name": "UPDATED"})])
244
+ check("update: edit succeeds", ok, str(res)[:160])
245
+ check("update: the id is unchanged",
246
+ ok and res.get("actionId") == btn_id, str(res)[:160])
247
+ check("update: the same workflow is reported",
248
+ ok and res.get("processId") == proc_id, str(res)[:160])
249
+ acts = _actions(ws_id)
250
+ check("update: still one action", len(acts) == 1,
251
+ str([(a.get("btnId"), a.get("name")) for a in acts]))
252
+ got = acts[0] if acts else {}
253
+ check("update: renamed in place",
254
+ got.get("btnId") == btn_id and got.get("name") == "UPDATED",
255
+ str((got.get("btnId"), got.get("name"))))
256
+ check("update: unnamed settings kept",
257
+ got.get("color") == "#2196F3" and got.get("clickType") == 1,
258
+ str((got.get("color"), got.get("clickType"))))
259
+ wf = [r for r in _workflow_rows(app_id)
260
+ if r.get("id") == proc_id or r.get("processId") == proc_id]
261
+ check("update: the action's workflow survives", bool(wf),
262
+ proc_id)
263
+
264
+ # A fill action edited by id stays a fill action and keeps its fields.
265
+ title = next((c["controlId"] for c in _fields(ws_id)
266
+ if c.get("attribute") == 1), "")
267
+ ok, res = _try(["worksheet", "create-custom-action", ws_id, "-a",
268
+ app_id, "--action-spec", json.dumps(
269
+ {"name": "FILL", "type": "updateCurrentRecord",
270
+ "updateFields": [title]})])
271
+ fill_id = res.get("actionId") if ok else ""
272
+ ok, res = _try(["worksheet", "create-custom-action", ws_id, "-a",
273
+ app_id, "--btn-id", fill_id, "--action-spec",
274
+ json.dumps({"name": "FILL2"})])
275
+ check("update: fill action edit succeeds", ok, str(res)[:160])
276
+ acts = _actions(ws_id)
277
+ fill = next((a for a in acts if a.get("btnId") == fill_id), {})
278
+ check("update: two actions after editing the second", len(acts) == 2,
279
+ str([(a.get("btnId"), a.get("name")) for a in acts]))
280
+ check("update: fill action keeps its kind and field",
281
+ fill.get("name") == "FILL2" and fill.get("clickType") == 3
282
+ and [w.get("controlId") for w in fill.get("writeControls")
283
+ or []] == [title],
284
+ str((fill.get("name"), fill.get("clickType"),
285
+ fill.get("writeControls"))))
286
+
287
+ ok, res = _try(["worksheet", "create-custom-action", ws_id, "-a",
288
+ app_id, "--btn-id", "000000000000000000000000",
289
+ "--action-spec", json.dumps({"name": "GHOST"})])
290
+ check("update: unknown id is refused", not ok, str(res)[:160])
291
+ check("update: nothing created for an unknown id",
292
+ len(_actions(ws_id)) == 2, str(len(_actions(ws_id))))
293
+ finally:
294
+ hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
295
+
296
+
297
+ def case_hidden_defaults(app_id: str, section_id: str) -> None:
298
+ """bug #455757: a hidden field's default goes in with a new record.
299
+
300
+ The form submits every non-empty cell on create, hidden ones included,
301
+ so the defaults the CLI fills in must not be judged as the caller
302
+ writing a hidden field. Writing one yourself is still refused.
303
+ """
304
+ print("scenario: hidden defaults on create")
305
+ ws_id = _make_worksheet(app_id, section_id, "LT-hidden-defaults")
306
+
307
+ def static(value):
308
+ return {"defsource": json.dumps(
309
+ [{"cid": "", "rcid": "", "staticValue": value}])}
310
+ try:
311
+ ok, res = _try(["worksheet", "add-fields", ws_id, "--controls",
312
+ json.dumps([
313
+ {"type": 2, "controlName": "Stage",
314
+ "fieldPermission": "110",
315
+ "advancedSetting": static("Draft")},
316
+ {"type": 2, "controlName": "Status",
317
+ "fieldPermission": "110",
318
+ "advancedSetting": static("Open")}])])
319
+ check("hidden: fields added", ok, str(res)[:160])
320
+ ids = {c.get("controlName"): c.get("controlId")
321
+ for c in _fields(ws_id)}
322
+ name_id = next((c["controlId"] for c in _fields(ws_id)
323
+ if c.get("attribute") == 1), "")
324
+ stage, status = ids.get("Stage"), ids.get("Status")
325
+
326
+ ok, res = _try(["worksheet", "record", "create", ws_id, "-a", app_id,
327
+ "--fields-json", json.dumps(
328
+ [{"id": name_id, "value": "visible-only"}])])
329
+ check("hidden: create with only the visible field succeeds", ok,
330
+ str(res)[:200])
331
+ row_id = ((res.get("data") or {}).get("rowid")
332
+ if ok and isinstance(res, dict) else "")
333
+ got = (hap(["worksheet", "record", "get", ws_id, row_id, "-a",
334
+ app_id]) or {}) if row_id else {}
335
+ got = got.get("data", got) if isinstance(got, dict) else {}
336
+ check("hidden: the hidden defaults were stored",
337
+ got.get(stage) == "Draft" and got.get(status) == "Open",
338
+ str({k: got.get(k) for k in (name_id, stage, status)}))
339
+
340
+ ok, res = _try(["worksheet", "record", "create", ws_id, "-a", app_id,
341
+ "--fields-json", json.dumps(
342
+ [{"id": name_id, "value": "sneaky"},
343
+ {"id": stage, "value": "Won"}])])
344
+ check("hidden: writing a hidden field yourself is refused",
345
+ not ok and "Stage" in str(res) and "Status" not in str(res),
346
+ str(res)[:240])
347
+
348
+ rows = json.dumps([{name_id: "batch-1"}, {name_id: "batch-2"}])
349
+ ok, res = _try(["worksheet", "record", "batch-create", ws_id,
350
+ "-a", app_id, "--rows", rows])
351
+ check("hidden: batch create with only visible fields succeeds", ok,
352
+ str(res)[:200])
353
+ rows = json.dumps([{name_id: "batch-3"},
354
+ {name_id: "batch-4", stage: "Won"}])
355
+ ok, res = _try(["worksheet", "record", "batch-create", ws_id,
356
+ "-a", app_id, "--rows", rows])
357
+ check("hidden: batch writing a hidden field is refused on its row",
358
+ not ok and "row 2" in str(res) and "row 1" not in str(res),
359
+ str(res)[:240])
360
+ listed = (hap(["worksheet", "record", "list", ws_id, "-a", app_id])
361
+ or {}).get("rows") or []
362
+ blob = json.dumps(listed, ensure_ascii=False)
363
+ check("hidden: the refused batch wrote nothing",
364
+ "batch-3" not in blob and "batch-1" in blob, blob[:200])
365
+ finally:
366
+ hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
367
+
368
+
229
369
  def _items(app_id: str) -> dict[str, str]:
230
370
  """All sidebar items (worksheets, pages, chatbots) name -> id."""
231
371
  detail = hap(["app", "info", "-a", app_id])
@@ -3397,6 +3537,154 @@ def case_fb_subtable_read(app_id: str, section_id: str) -> None:
3397
3537
  hap(["worksheet", "delete", parent, "--yes"], check=False)
3398
3538
 
3399
3539
 
3540
+ def case_editor_inline_subtable(app_id: str, section_id: str) -> None:
3541
+ """Bug #455768: edit-spec field ops on an inline sub-table's own sheet."""
3542
+ print("\nscenario: editor field ops on an inline sub-table (#455768)")
3543
+ import json as _json
3544
+ name = "BUG455768-sub-parent"
3545
+ parent = _make_worksheet(app_id, section_id, name)
3546
+ check("inline-subtable: a throwaway parent was created", bool(parent))
3547
+ if not parent:
3548
+ return
3549
+ try:
3550
+ hap(["worksheet", "update-fields", parent, "--title-name", "名称",
3551
+ "--no-verify", "--fields", _json.dumps([
3552
+ {"type": "TEXT", "name": "名称"},
3553
+ {"type": "SUB_LIST", "name": "内嵌明细", "child_fields": [
3554
+ {"type": "TEXT", "name": "行名称"},
3555
+ {"type": "NUMBER", "name": "行数量"}]},
3556
+ ], ensure_ascii=False)], check=False)
3557
+ child = next((c.get("dataSource") for c in _fields(parent)
3558
+ if c.get("type") == 34), "")
3559
+ check("inline-subtable: the parent names its child sheet",
3560
+ bool(child), str(child))
3561
+ if not child:
3562
+ return
3563
+
3564
+ def _child_cols() -> list[dict]:
3565
+ got = hap(["worksheet", "fields", child, "--raw",
3566
+ "--parent", parent], check=False)
3567
+ return [c for c in got if len(c.get("controlId") or "") == 24] \
3568
+ if isinstance(got, list) else []
3569
+
3570
+ rc, out, err = editor_apply({"app": app_id, "ops": [
3571
+ {"type": "field.update", "worksheet": child, "field": "行名称",
3572
+ "set": {"hint": "按id改"}}]})
3573
+ check("inline-subtable: field.update by sheet id applies", rc == 0,
3574
+ (err or out)[-300:])
3575
+ cols = {c["controlName"]: c for c in _child_cols()}
3576
+ check("inline-subtable: the hint reads back",
3577
+ (cols.get("行名称") or {}).get("hint") == "按id改",
3578
+ str(cols.get("行名称"))[:200])
3579
+
3580
+ rc, out, err = editor_apply({"app": app_id, "ops": [
3581
+ {"type": "field.update", "worksheet": f"{name}/内嵌明细",
3582
+ "field": "行数量", "rename": "数量", "set": {}},
3583
+ {"type": "field.add", "worksheet": child,
3584
+ "field": {"name": "行备注", "type": "Text"}},
3585
+ {"type": "field.reorder", "worksheet": child,
3586
+ "order": ["行备注", "数量"]},
3587
+ {"type": "field.delete", "worksheet": child, "field": "行名称",
3588
+ "confirm": True}]})
3589
+ check("inline-subtable: add / reorder / delete apply", rc == 0,
3590
+ (err or out)[-300:])
3591
+ names = [c["controlName"] for c in
3592
+ sorted(_child_cols(), key=lambda c: c.get("row", 0))]
3593
+ check("inline-subtable: columns read back renamed, added, "
3594
+ "reordered and deleted", names == ["行备注", "数量"],
3595
+ str(names))
3596
+ sub = next((c for c in _fields(parent) if c.get("type") == 34), {})
3597
+ shown = {c["controlId"]: c["controlName"] for c in _child_cols()}
3598
+ check("inline-subtable: the new column is shown in the sub-table",
3599
+ [shown.get(i) for i in sub.get("showControls") or []]
3600
+ == ["行备注", "数量"], str(sub.get("showControls")))
3601
+ finally:
3602
+ hap(["worksheet", "delete", parent, "-a", app_id, "-y"], check=False)
3603
+
3604
+
3605
+ def case_create_partial(app_id: str, section_id: str) -> None:
3606
+ """Bug #455775: a failed `worksheet create --fields` leaves nothing."""
3607
+ print("\nscenario: worksheet create --fields failure (#455775)")
3608
+ import json as _json
3609
+ from hap_cli.core import app as app_mod
3610
+ from hap_cli.core.session import Session
3611
+ session = Session.load()
3612
+ project_id = app_mod.app_project_id(session, app_id)
3613
+ prefix = "QA455775"
3614
+ for leftover in [n for n in _worksheets(app_id) if n.startswith(prefix)]:
3615
+ _ensure_absent(app_id, leftover)
3616
+
3617
+ def _ours() -> list[str]:
3618
+ return [n for n in _worksheets(app_id) if n.startswith(prefix)]
3619
+
3620
+ def _purge(ws_id: str) -> None:
3621
+ try:
3622
+ app_mod.purge_app_item(session, app_id=app_id,
3623
+ project_id=project_id,
3624
+ section_id=section_id, item_id=ws_id,
3625
+ item_type=0)
3626
+ except Exception as exc: # noqa: BLE001 — reported, not fatal
3627
+ print(f" note: could not purge {ws_id}: {exc}")
3628
+
3629
+ single = {"source": "static", "value": "DEFAULT-QA"}
3630
+ ok, res = _try(["worksheet", "create", app_id, f"{prefix} Invalid",
3631
+ "--section-id", section_id, "--fields", _json.dumps([
3632
+ {"type": "Text", "name": "名称"},
3633
+ {"type": "Text", "name": "备注",
3634
+ "defaultValue": single}], ensure_ascii=False)])
3635
+ check("create-partial: a single-object default is refused", not ok,
3636
+ str(res)[:200])
3637
+ check("create-partial: the refusal names the field and the shape",
3638
+ "备注" in str(res) and "list of objects" in str(res),
3639
+ str(res)[:300])
3640
+ check("create-partial: nothing was created", _ours() == [], str(_ours()))
3641
+
3642
+ # Passes every offline check, refused by the server at save time:
3643
+ # an alias the worksheet's own record-id column already holds.
3644
+ ok, res = _try(["worksheet", "create", app_id, f"{prefix} PostFail",
3645
+ "--section-id", section_id, "--fields", _json.dumps([
3646
+ {"type": "Text", "name": "名称"},
3647
+ {"type": "Text", "name": "冲突",
3648
+ "extra": {"alias": "rowid"}}],
3649
+ ensure_ascii=False)])
3650
+ check("create-partial: a save rejected after the create fails", not ok,
3651
+ str(res)[:200])
3652
+ check("create-partial: the error says the create was undone",
3653
+ "undone" in str(res), str(res)[:300])
3654
+ check("create-partial: no half-built worksheet is left",
3655
+ _ours() == [], str(_ours()))
3656
+
3657
+ ws_id = ""
3658
+ try:
3659
+ made = hap(["worksheet", "create", app_id, f"{prefix} Array",
3660
+ "--section-id", section_id, "--fields", _json.dumps([
3661
+ {"type": "Text", "name": "名称"},
3662
+ {"type": "Text", "name": "备注",
3663
+ "defaultValue": [single]}], ensure_ascii=False)],
3664
+ check=False)
3665
+ ws_id = made.get("worksheetId", "") if isinstance(made, dict) else ""
3666
+ check("create-partial: the list form still creates", bool(ws_id),
3667
+ str(made)[:200])
3668
+ if not ws_id:
3669
+ return
3670
+ note = next((c for c in _fields(ws_id)
3671
+ if c.get("controlName") == "备注"), {})
3672
+ check("create-partial: its default is stored",
3673
+ "DEFAULT-QA" in str((note.get("advancedSetting") or {})
3674
+ .get("defsource")), str(note)[:200])
3675
+ ok, res = _try(["worksheet", "update-fields", ws_id, "--fields",
3676
+ _json.dumps([{"type": "Text", "name": "名称"},
3677
+ {"type": "Text", "name": "备注",
3678
+ "defaultValue": single}],
3679
+ ensure_ascii=False)])
3680
+ check("create-partial: update-fields refuses it clearly too",
3681
+ not ok and "defaultValue" in str(res)
3682
+ and "AttributeError" not in str(res), str(res)[:300])
3683
+ finally:
3684
+ if ws_id:
3685
+ _purge(ws_id)
3686
+
3687
+
3400
3688
  def case_fb_subgroups(app_id: str, section_id: str) -> None:
3401
3689
  """WO-FB 3.1: the editor can see worksheets in second-level groups."""
3402
3690
  print("\nscenario: sub-group visibility (WO-FB 3.1)")
@@ -4546,6 +4834,8 @@ _CASES = {
4546
4834
  "view": case_view,
4547
4835
  "role": case_role,
4548
4836
  "custom-action": case_custom_action,
4837
+ "custom-action-update": case_custom_action_update,
4838
+ "hidden-defaults": case_hidden_defaults,
4549
4839
  "chatbot": case_chatbot,
4550
4840
  "custom-page": case_custom_page,
4551
4841
  "component": case_component,
@@ -4585,6 +4875,8 @@ _CASES = {
4585
4875
  "fb-layout-check": case_fb_layout_check,
4586
4876
  "fb-pair-relation": case_fb_pair_relation,
4587
4877
  "fb-subtable-read": case_fb_subtable_read,
4878
+ "create-partial": case_create_partial,
4879
+ "editor-inline-subtable": case_editor_inline_subtable,
4588
4880
  "fb-subgroups": case_fb_subgroups,
4589
4881
  "fb-button-target": case_fb_button_target,
4590
4882
  "g6-advanced-fields": case_g6_advanced_fields,
@@ -3,7 +3,7 @@
3
3
  import sys as _sys
4
4
  import types as _types
5
5
 
6
- __version__ = "0.9.1"
6
+ __version__ = "0.9.3"
7
7
 
8
8
  # The command tree used to live in hap_cli/hap_cli.py. A module named
9
9
  # after the package it sits in shadows that package: run python with the
@@ -1299,7 +1299,7 @@ def worksheet_save_button(ctx, worksheet_id, btn_config, action_spec,
1299
1299
  eff_app = app_id or getattr(session, "default_app_id", "") or ""
1300
1300
  data = ws_mod.create_custom_action(
1301
1301
  session, worksheet_id, json.loads(action_spec),
1302
- app_id=eff_app,
1302
+ app_id=eff_app, btn_id=btn_id,
1303
1303
  )
1304
1304
  else:
1305
1305
  btn_data = json.loads(btn_config)
@@ -84,6 +84,11 @@ class Context:
84
84
  is_auth = isinstance(e, (AuthenticationError, SessionError))
85
85
  if self.json_mode:
86
86
  payload = {"error": msg, "type": type(e).__name__}
87
+ # An error may carry state the caller needs to act on, e.g.
88
+ # the id of a worksheet a failed create left behind.
89
+ details = getattr(e, "details", None)
90
+ if isinstance(details, dict):
91
+ payload.update(details)
87
92
  if is_auth:
88
93
  payload["hint"] = NOT_LOGGED_IN_HINT
89
94
  payload["not_logged_in"] = True
@@ -15,6 +15,8 @@ the plain CLI commands — see ``editspec.RETIRED_OPS``.
15
15
  """
16
16
  from __future__ import annotations
17
17
 
18
+ import json
19
+ import uuid
18
20
  from typing import Any, Callable
19
21
 
20
22
  from hap_cli.core import page as page_mod
@@ -31,6 +33,61 @@ def _ws_field_save(s, ws_id, controls):
31
33
  return ws_mod.save_controls(s, ws_id, controls)
32
34
 
33
35
 
36
+ # ── inline sub-table sheets: written through the parent (bug #455768) ───
37
+ # Such a sheet cannot be saved on its own; the product edits its columns
38
+ # as the parent's sub-table column ``relationControls`` and saves the
39
+ # parent. See ``AppIndex._subtable_map`` for the evidence.
40
+ def _subtable_columns(idx: AppIndex, ws_ref: str):
41
+ """``(parent_id, parent controls, host column)`` copies, or None.
42
+
43
+ The host column and its ``relationControls`` are copied so the
44
+ mutation never leaks into the index cache.
45
+ """
46
+ host = idx.subtable_host(ws_ref)
47
+ if host is None:
48
+ return None
49
+ parent_id, column_id = host
50
+ controls = [dict(c) for c in idx.controls(parent_id)]
51
+ column = next((c for c in controls if c.get("controlId") == column_id),
52
+ None)
53
+ if column is None:
54
+ raise ResolveError(
55
+ f"the sub-table column hosting worksheet {ws_ref!r} is gone")
56
+ column["relationControls"] = [dict(r) for r in
57
+ column.get("relationControls") or []]
58
+ return parent_id, controls, column
59
+
60
+
61
+ def _sorts_of(column: dict) -> tuple[dict, list]:
62
+ adv = column.get("advancedSetting")
63
+ if isinstance(adv, str):
64
+ try:
65
+ adv = json.loads(adv) if adv else {}
66
+ except ValueError:
67
+ adv = {}
68
+ adv = dict(adv or {})
69
+ try:
70
+ sorts = json.loads(adv.get("controlssorts") or "[]")
71
+ except (TypeError, ValueError):
72
+ sorts = []
73
+ return adv, sorts if isinstance(sorts, list) else []
74
+
75
+
76
+ def _set_display(column: dict, show: list, sorts: list) -> None:
77
+ """Keep ``showControls`` and ``controlssorts`` in step with the
78
+ columns — the product updates both on every add/delete/reorder, or
79
+ the sub-table renders its columns out of order (MDPublic
80
+ ConfigureControls.jsx ``updateControlsWithSorts``)."""
81
+ adv, _ = _sorts_of(column)
82
+ adv["controlssorts"] = json.dumps(sorts)
83
+ column["advancedSetting"] = adv
84
+ column["showControls"] = show
85
+
86
+
87
+ def _sub_label(ws_ref: str) -> str:
88
+ return f"'{ws_ref}' (sub-table, saved through its parent)"
89
+
90
+
34
91
  def _far_worksheet_of(controls: list[dict], bridge_id: str) -> str:
35
92
  """The worksheet a bridge column reaches, from its own dataSource."""
36
93
  for c in controls:
@@ -144,6 +201,27 @@ def _field_add(op: dict[str, Any], idx: AppIndex) -> list[Action]:
144
201
  # place.
145
202
  control = lower_field(field)
146
203
 
204
+ sub = _subtable_columns(idx, op["worksheet"])
205
+ if sub is not None:
206
+ if control.get("type") == 34:
207
+ raise ResolveError(
208
+ f"cannot add sub-table {field.get('name')!r} inside the "
209
+ f"sub-table {op['worksheet']!r}")
210
+ parent_id, controls, column = sub
211
+ control = dict(control)
212
+ # A dashed placeholder: the server mints the real id and rewrites
213
+ # it in showControls too (verified live 2026-10-08).
214
+ control["controlId"] = str(uuid.uuid4())
215
+ column["relationControls"].append(control)
216
+ _, sorts = _sorts_of(column)
217
+ _set_display(column,
218
+ list(column.get("showControls") or [])
219
+ + [control["controlId"]],
220
+ sorts + [control["controlId"]])
221
+ return [Action(f"add field '{op['field']['name']}' to "
222
+ f"{_sub_label(op['worksheet'])}",
223
+ lambda s: _ws_field_save(s, parent_id, controls))]
224
+
147
225
  # Mounting an existing worksheet is a two-step handshake — build the
148
226
  # SUB_LIST, then wire the child side's back-relation — and
149
227
  # `mount_existing_subtable` is the composite that already does both.
@@ -179,11 +257,20 @@ def _match_control(controls: list, ref: str) -> dict:
179
257
 
180
258
  def _field_update(op: dict[str, Any], idx: AppIndex) -> list[Action]:
181
259
  ws_id = idx.worksheet_id(op["worksheet"])
182
- controls = [dict(c) for c in idx.controls(op["worksheet"])]
183
- target = _match_control(controls, op["field"])
260
+ sub = _subtable_columns(idx, op["worksheet"])
261
+ if sub is not None:
262
+ parent_id, controls, column = sub
263
+ target = _match_control(column["relationControls"], op["field"])
264
+ else:
265
+ controls = [dict(c) for c in idx.controls(op["worksheet"])]
266
+ target = _match_control(controls, op["field"])
184
267
  if op.get("rename"):
185
268
  target["controlName"] = op["rename"]
186
269
  target.update(op.get("set", {}))
270
+ if sub is not None:
271
+ return [Action(f"update field '{op['field']}' on "
272
+ f"{_sub_label(op['worksheet'])}",
273
+ lambda s: _ws_field_save(s, parent_id, controls))]
187
274
  return [Action(f"update field '{op['field']}' on '{op['worksheet']}' "
188
275
  f"(write-back {len(controls)} controls)",
189
276
  lambda s: _ws_field_save(s, ws_id, controls))]
@@ -191,6 +278,21 @@ def _field_update(op: dict[str, Any], idx: AppIndex) -> list[Action]:
191
278
 
192
279
  def _field_delete(op: dict[str, Any], idx: AppIndex) -> list[Action]:
193
280
  ws_id = idx.worksheet_id(op["worksheet"])
281
+ sub = _subtable_columns(idx, op["worksheet"])
282
+ if sub is not None:
283
+ parent_id, controls, column = sub
284
+ gone = _match_control(column["relationControls"],
285
+ op["field"]).get("controlId")
286
+ column["relationControls"] = [r for r in column["relationControls"]
287
+ if r.get("controlId") != gone]
288
+ _, sorts = _sorts_of(column)
289
+ _set_display(column,
290
+ [i for i in column.get("showControls") or []
291
+ if i != gone],
292
+ [i for i in sorts if i != gone])
293
+ return [Action(f"delete field '{op['field']}' from "
294
+ f"{_sub_label(op['worksheet'])}",
295
+ lambda s: _ws_field_save(s, parent_id, controls))]
194
296
  controls = idx.controls(op["worksheet"])
195
297
  target = _match_control(controls, op["field"])
196
298
  remaining = [c for c in controls
@@ -202,7 +304,12 @@ def _field_delete(op: dict[str, Any], idx: AppIndex) -> list[Action]:
202
304
 
203
305
  def _field_reorder(op: dict[str, Any], idx: AppIndex) -> list[Action]:
204
306
  ws_id = idx.worksheet_id(op["worksheet"])
205
- controls = [dict(c) for c in idx.controls(op["worksheet"])]
307
+ sub = _subtable_columns(idx, op["worksheet"])
308
+ if sub is not None:
309
+ parent_id, parent_controls, column = sub
310
+ controls = column["relationControls"]
311
+ else:
312
+ controls = [dict(c) for c in idx.controls(op["worksheet"])]
206
313
  by_ref: dict[str, dict] = {}
207
314
  for c in controls:
208
315
  for k in (c.get("controlId"), c.get("controlName"), c.get("alias")):
@@ -225,6 +332,15 @@ def _field_reorder(op: dict[str, Any], idx: AppIndex) -> list[Action]:
225
332
  for i, c in enumerate(ordered):
226
333
  c["row"] = i
227
334
  c["col"] = 0
335
+ if sub is not None:
336
+ column["relationControls"] = ordered
337
+ ids = [c.get("controlId") for c in ordered]
338
+ shown = set(column.get("showControls") or [])
339
+ _set_display(column, [i for i in ids if i in shown], ids)
340
+ return [Action(f"reorder {len(op['order'])} field(s) on "
341
+ f"{_sub_label(op['worksheet'])}",
342
+ lambda s: _ws_field_save(s, parent_id,
343
+ parent_controls))]
228
344
  return [Action(f"reorder {len(op['order'])} field(s) on '{op['worksheet']}'",
229
345
  lambda s: _ws_field_save(s, ws_id, ordered))]
230
346