hap-cli 0.9.1__tar.gz → 0.9.2__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 (412) hide show
  1. {hap_cli-0.9.1 → hap_cli-0.9.2}/PKG-INFO +1 -1
  2. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/smoke.py +208 -0
  3. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/__init__.py +1 -1
  4. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/worksheet_cmd.py +1 -1
  5. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/ops.py +119 -3
  6. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/reader.py +137 -1
  7. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/record.py +6 -2
  8. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/record_form_checks.py +30 -4
  9. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/worksheet.py +157 -37
  10. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_core.py +171 -0
  11. hap_cli-0.9.2/hap_cli/tests/test_editor_inline_subtable.py +239 -0
  12. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g6_advanced_fields.py +5 -0
  13. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_form_checks.py +126 -0
  14. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/PKG-INFO +1 -1
  15. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/SOURCES.txt +1 -0
  16. {hap_cli-0.9.1 → hap_cli-0.9.2}/MANIFEST.in +0 -0
  17. {hap_cli-0.9.1 → hap_cli-0.9.2}/README.md +0 -0
  18. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/__init__.py +0 -0
  19. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/__main__.py +0 -0
  20. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/config.py +0 -0
  21. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/fixtures/__init__.py +0 -0
  22. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/fixtures/feedback_scenario.py +0 -0
  23. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/harness.py +0 -0
  24. {hap_cli-0.9.1 → hap_cli-0.9.2}/app_live_tests/state.py +0 -0
  25. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/README.md +0 -0
  26. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/README_CN.md +0 -0
  27. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/__main__.py +0 -0
  28. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/_entry.py +0 -0
  29. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/__init__.py +0 -0
  30. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/app_cmd.py +0 -0
  31. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/app_creator_cmd.py +0 -0
  32. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/app_editor_cmd.py +0 -0
  33. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/attachment_cmd.py +0 -0
  34. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/auth_cmd.py +0 -0
  35. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/calendar_cmd.py +0 -0
  36. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/chart_cmd.py +0 -0
  37. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/chat_cmd.py +0 -0
  38. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/chatbot_cmd.py +0 -0
  39. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/config_cmd.py +0 -0
  40. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/contact_cmd.py +0 -0
  41. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/department_cmd.py +0 -0
  42. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/group_cmd.py +0 -0
  43. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/guide_cmd.py +0 -0
  44. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/icon_cmd.py +0 -0
  45. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/instance_cmd.py +0 -0
  46. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/node_cmd.py +0 -0
  47. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/optionset_cmd.py +0 -0
  48. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/page_cmd.py +0 -0
  49. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/post_cmd.py +0 -0
  50. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/record_cmd.py +0 -0
  51. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/region_cmd.py +0 -0
  52. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/role_cmd.py +0 -0
  53. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/update_cmd.py +0 -0
  54. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/upload_cmd.py +0 -0
  55. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/v3_registry.py +0 -0
  56. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/commands/workflow_cmd.py +0 -0
  57. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/context.py +0 -0
  58. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/__init__.py +0 -0
  59. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/action_spec_adapter.py +0 -0
  60. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app.py +0 -0
  61. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/__init__.py +0 -0
  62. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  63. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  64. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  65. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/charts.py +0 -0
  66. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/cleanup.py +0 -0
  67. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/compiler.py +0 -0
  68. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/config.py +0 -0
  69. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  70. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/errors.py +0 -0
  71. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  72. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/executor.py +0 -0
  73. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/fields.py +0 -0
  74. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/hap.py +0 -0
  75. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/merge.py +0 -0
  76. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  77. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/recording/console.py +0 -0
  78. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  79. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  80. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/recording/report.py +0 -0
  81. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/schema.py +0 -0
  82. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  83. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  84. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/cli.py +0 -0
  85. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/executor.py +0 -0
  86. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  87. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  88. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  89. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  90. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/seed/template.py +0 -0
  91. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/selftest.py +0 -0
  92. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/smoke.py +0 -0
  93. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/steps.py +0 -0
  94. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/store.py +0 -0
  95. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/validate.py +0 -0
  96. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  97. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/attachment.py +0 -0
  98. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/auth.py +0 -0
  99. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/calendar_mod.py +0 -0
  100. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/chart.py +0 -0
  101. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/chat.py +0 -0
  102. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/chatbot.py +0 -0
  103. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/cli_access.py +0 -0
  104. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/condition_dsl.py +0 -0
  105. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/contact.py +0 -0
  106. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/control_type_codes.py +0 -0
  107. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/department.py +0 -0
  108. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/__init__.py +0 -0
  109. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  110. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  111. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/apply.py +0 -0
  112. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/componentlower.py +0 -0
  113. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  114. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  115. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  116. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  117. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/editspec.py +0 -0
  118. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/errors.py +0 -0
  119. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/fieldlower.py +0 -0
  120. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  121. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/models.py +0 -0
  122. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/editor/planner.py +0 -0
  123. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/field_normalizer.py +0 -0
  124. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/field_spec_adapter.py +0 -0
  125. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/filter_translator.py +0 -0
  126. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/filter_v3.py +0 -0
  127. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/flow_node.py +0 -0
  128. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/global_meta.py +0 -0
  129. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/group.py +0 -0
  130. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/guide.py +0 -0
  131. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/icon_index.py +0 -0
  132. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/instance.py +0 -0
  133. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/layout_check.py +0 -0
  134. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/logger.py +0 -0
  135. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/optionset.py +0 -0
  136. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/page.py +0 -0
  137. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/post.py +0 -0
  138. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/record_defaults.py +0 -0
  139. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/record_lookups.py +0 -0
  140. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/record_rules.py +0 -0
  141. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/relation_scope.py +0 -0
  142. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/response_crypto.py +0 -0
  143. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/role.py +0 -0
  144. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/role_perm_builder.py +0 -0
  145. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/rule_spec_adapter.py +0 -0
  146. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/session.py +0 -0
  147. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/token_crypto.py +0 -0
  148. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/update.py +0 -0
  149. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/upload.py +0 -0
  150. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/v3/__init__.py +0 -0
  151. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/v3/dispatcher.py +0 -0
  152. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/v3/render.py +0 -0
  153. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/v3/schema.py +0 -0
  154. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/view_spec_adapter.py +0 -0
  155. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/workflow.py +0 -0
  156. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/workflow_node_dsl.py +0 -0
  157. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/worksheet_form.py +0 -0
  158. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/worksheet_reference.py +0 -0
  159. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/core/worksheet_templates.py +0 -0
  160. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/approval.md +0 -0
  161. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/chart.md +0 -0
  162. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/messages.md +0 -0
  163. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/overview.md +0 -0
  164. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/post.md +0 -0
  165. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/record.md +0 -0
  166. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/setup.md +0 -0
  167. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/skills.md +0 -0
  168. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/workflow.md +0 -0
  169. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/en/worksheet.md +0 -0
  170. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/index.json +0 -0
  171. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/approval.md +0 -0
  172. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/chart.md +0 -0
  173. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/messages.md +0 -0
  174. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/overview.md +0 -0
  175. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/post.md +0 -0
  176. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/record.md +0 -0
  177. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/setup.md +0 -0
  178. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/skills.md +0 -0
  179. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/workflow.md +0 -0
  180. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
  181. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/i18n.py +0 -0
  182. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/locale/__init__.py +0 -0
  183. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/locale/messages.json +0 -0
  184. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/locale/messages.schema.json +0 -0
  185. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/__init__.py +0 -0
  186. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/conftest.py +0 -0
  187. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
  188. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
  189. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_app_creator.py +0 -0
  190. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_app_editor.py +0 -0
  191. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_app_export_usage.py +0 -0
  192. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_app_light_color.py +0 -0
  193. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_app_section_name.py +0 -0
  194. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
  195. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_approval_wire.py +0 -0
  196. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_atomic_write.py +0 -0
  197. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_attachment_share.py +0 -0
  198. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_auth_prerelease.py +0 -0
  199. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_batch_add_atomic.py +0 -0
  200. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_bug_036430.py +0 -0
  201. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_bug_036750.py +0 -0
  202. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_calendar_date_range.py +0 -0
  203. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
  204. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chart.py +0 -0
  205. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chart_filter_merge.py +0 -0
  206. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  207. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chat_messages_cli.py +0 -0
  208. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chat_recall_cli.py +0 -0
  209. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_chat_send_socket.py +0 -0
  210. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_cli_access.py +0 -0
  211. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_coded_responses.py +0 -0
  212. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_condition_dsl.py +0 -0
  213. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
  214. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
  215. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_config_cmd.py +0 -0
  216. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_content_language.py +0 -0
  217. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  218. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  219. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
  220. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
  221. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_engine_core_parity.py +0 -0
  222. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
  223. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_bidirectional.py +0 -0
  224. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
  225. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_entry_module.py +0 -0
  226. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
  227. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_http_error_body.py +0 -0
  228. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
  229. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
  230. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_layout_check.py +0 -0
  231. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
  232. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_node_summary.py +0 -0
  233. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_post_cursor.py +0 -0
  234. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
  235. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
  236. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
  237. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_subgroups.py +0 -0
  238. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_subtable_read.py +0 -0
  239. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
  240. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
  241. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
  242. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
  243. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_full_e2e.py +0 -0
  244. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g1_chart_parity.py +0 -0
  245. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g1_record_parity.py +0 -0
  246. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g1_upload_parity.py +0 -0
  247. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
  248. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
  249. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
  250. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g4_approval_parity.py +0 -0
  251. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
  252. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
  253. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
  254. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
  255. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g8_filter_operator.py +0 -0
  256. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
  257. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
  258. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
  259. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_global_meta.py +0 -0
  260. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_guide.py +0 -0
  261. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_guide_sections.py +0 -0
  262. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_i18n.py +0 -0
  263. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
  264. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_instance_envelope.py +0 -0
  265. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_instance_history_cli.py +0 -0
  266. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration.py +0 -0
  267. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_approval.py +0 -0
  268. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  269. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_calendar.py +0 -0
  270. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_destructive.py +0 -0
  271. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_misc.py +0 -0
  272. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_post.py +0 -0
  273. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_social.py +0 -0
  274. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_v3.py +0 -0
  275. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_workflow.py +0 -0
  276. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  277. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
  278. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  279. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_multi_profile.py +0 -0
  280. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  281. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
  282. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  283. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_org_id_cli.py +0 -0
  284. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_org_id_docs.py +0 -0
  285. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_page_filters_group_read.py +0 -0
  286. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_page_filters_group_update.py +0 -0
  287. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_page_save_page_id.py +0 -0
  288. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_parameter_conventions.py +0 -0
  289. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  290. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_post_scope_cli.py +0 -0
  291. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  292. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_business_rules.py +0 -0
  293. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_defaults.py +0 -0
  294. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
  295. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_field_alias.py +0 -0
  296. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_run_action.py +0 -0
  297. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  298. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_record_write_fixes.py +0 -0
  299. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_relation_scope.py +0 -0
  300. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
  301. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_role_app_scope.py +0 -0
  302. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
  303. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
  304. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_save_rule_spec.py +0 -0
  305. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_select_add_option.py +0 -0
  306. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  307. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  308. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
  309. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_update.py +0 -0
  310. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_update_mode.py +0 -0
  311. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_upload_allow_down.py +0 -0
  312. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  313. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
  314. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  315. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_bool_flags.py +0 -0
  316. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  317. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  318. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_response_format_json.py +0 -0
  319. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_session.py +0 -0
  320. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  321. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
  322. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_config_keys.py +0 -0
  323. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_inner_scope.py +0 -0
  324. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
  325. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
  326. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
  327. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_structure_listing.py +0 -0
  328. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
  329. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
  330. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_wo4_result_handling.py +0 -0
  331. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_code_node.py +0 -0
  332. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
  333. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
  334. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
  335. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  336. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
  337. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  338. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_node_wire.py +0 -0
  339. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
  340. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  341. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  342. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  343. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  344. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
  345. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_worksheet_switch.py +0 -0
  346. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  347. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/__init__.py +0 -0
  348. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/atomic_write.py +0 -0
  349. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/confirm.py +0 -0
  350. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/formatting.py +0 -0
  351. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/options.py +0 -0
  352. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli/utils/parameter_mapping.py +0 -0
  353. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/dependency_links.txt +0 -0
  354. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/entry_points.txt +0 -0
  355. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/requires.txt +0 -0
  356. {hap_cli-0.9.1 → hap_cli-0.9.2}/hap_cli.egg-info/top_level.txt +0 -0
  357. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/__init__.py +0 -0
  358. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/__main__.py +0 -0
  359. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/_wftest.py +0 -0
  360. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/charts.py +0 -0
  361. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/cleanup.py +0 -0
  362. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/compiler.py +0 -0
  363. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/config.py +0 -0
  364. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/errors.py +0 -0
  365. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/executor.py +0 -0
  366. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/fields.py +0 -0
  367. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/hap.py +0 -0
  368. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/recording/__init__.py +0 -0
  369. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/recording/console.py +0 -0
  370. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/recording/jsonl.py +0 -0
  371. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/recording/mirror.py +0 -0
  372. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/recording/report.py +0 -0
  373. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/schema.py +0 -0
  374. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/seed/__init__.py +0 -0
  375. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/seed/cli.py +0 -0
  376. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/seed/executor.py +0 -0
  377. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/seed/template.py +0 -0
  378. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/selftest.py +0 -0
  379. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/smoke.py +0 -0
  380. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/step.py +0 -0
  381. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/steps.py +0 -0
  382. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/store.py +0 -0
  383. {hap_cli-0.9.1 → hap_cli-0.9.2}/live_tests/workflow_dsl.py +0 -0
  384. {hap_cli-0.9.1 → hap_cli-0.9.2}/setup.cfg +0 -0
  385. {hap_cli-0.9.1 → hap_cli-0.9.2}/setup.py +0 -0
  386. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/INDEX.json +0 -0
  387. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  388. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  389. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/create_optionset.yaml +0 -0
  390. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/create_role.yaml +0 -0
  391. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  392. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/delete_record.yaml +0 -0
  393. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/delete_role.yaml +0 -0
  394. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  395. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_app_info.yaml +0 -0
  396. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  397. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  398. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  399. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_details.yaml +0 -0
  400. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  401. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_list.yaml +0 -0
  402. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  403. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  404. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  405. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  406. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_regions.yaml +0 -0
  407. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_role_details.yaml +0 -0
  408. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/get_role_list.yaml +0 -0
  409. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  410. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  411. {hap_cli-0.9.1 → hap_cli-0.9.2}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  412. {hap_cli-0.9.1 → hap_cli-0.9.2}/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.2
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,71 @@ 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
+
3400
3605
  def case_fb_subgroups(app_id: str, section_id: str) -> None:
3401
3606
  """WO-FB 3.1: the editor can see worksheets in second-level groups."""
3402
3607
  print("\nscenario: sub-group visibility (WO-FB 3.1)")
@@ -4546,6 +4751,8 @@ _CASES = {
4546
4751
  "view": case_view,
4547
4752
  "role": case_role,
4548
4753
  "custom-action": case_custom_action,
4754
+ "custom-action-update": case_custom_action_update,
4755
+ "hidden-defaults": case_hidden_defaults,
4549
4756
  "chatbot": case_chatbot,
4550
4757
  "custom-page": case_custom_page,
4551
4758
  "component": case_component,
@@ -4585,6 +4792,7 @@ _CASES = {
4585
4792
  "fb-layout-check": case_fb_layout_check,
4586
4793
  "fb-pair-relation": case_fb_pair_relation,
4587
4794
  "fb-subtable-read": case_fb_subtable_read,
4795
+ "editor-inline-subtable": case_editor_inline_subtable,
4588
4796
  "fb-subgroups": case_fb_subgroups,
4589
4797
  "fb-button-target": case_fb_button_target,
4590
4798
  "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.2"
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)
@@ -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
 
@@ -13,6 +13,7 @@ reference an element a previous op just created (intra-spec chaining).
13
13
  """
14
14
  from __future__ import annotations
15
15
 
16
+ import logging
16
17
  from typing import Any, Optional
17
18
 
18
19
  from hap_cli.core import app as app_mod
@@ -24,6 +25,8 @@ from hap_cli.core import workflow as wf_mod
24
25
  from hap_cli.core.editor.errors import ResolveError
25
26
  from hap_cli.core.session import Session
26
27
 
28
+ logger = logging.getLogger("hap_cli.editor")
29
+
27
30
 
28
31
  class AppIndex:
29
32
  def __init__(self, session: Session, app_id: str, org_id: str = "",
@@ -35,6 +38,8 @@ class AppIndex:
35
38
  self._sections: list[dict] = []
36
39
  self._ws_controls: dict[str, list] = {}
37
40
  self._ws_views: dict[str, list] = {}
41
+ self._subtables: Optional[dict[str, dict]] = None
42
+ self._subtable_kind: dict[str, Any] = {}
38
43
  if app_detail:
39
44
  self._index_sections(app_detail)
40
45
 
@@ -115,6 +120,8 @@ class AppIndex:
115
120
  self._index_sections(detail)
116
121
  self._ws_controls.clear()
117
122
  self._ws_views.clear()
123
+ self._subtables = None
124
+ self._subtable_kind.clear()
118
125
  for attr in ("_roles_cache", "_btn_cache", "_wf_cache"):
119
126
  if hasattr(self, attr):
120
127
  delattr(self, attr)
@@ -145,8 +152,133 @@ class AppIndex:
145
152
  for _sid, item in self._ws_items():
146
153
  if item.get("id") == ref or item.get("name") == ref:
147
154
  return item.get("id")
155
+ child = self._subtable_sheet(ref)
156
+ if child:
157
+ return child
148
158
  raise ResolveError(f"worksheet {ref!r} not found in app {self.app_id}")
149
159
 
160
+ # ── inline sub-table sheets (not in navigation) ─────────────────
161
+ #
162
+ # An inline sub-table keeps its rows in a worksheet of its own, named
163
+ # by the sub-table column's ``dataSource``. That sheet never appears
164
+ # among the app's navigation items, so resolving only those left it
165
+ # unreachable (bug #455768: field.update on it -> "worksheet not
166
+ # found"). It is found here instead by scanning the type-34 columns
167
+ # of the navigation worksheets — only after a navigation miss, so an
168
+ # ordinary reference costs no extra request.
169
+ #
170
+ # The product edits such a sheet's columns through the parent: they
171
+ # ride in the parent column's ``relationControls`` and the parent's
172
+ # SaveWorksheetControls stores them (MDPublic src/pages/widgetConfig/
173
+ # widgetSetting/components/sublist/ConfigureControls.jsx
174
+ # handleControlDataChange / addControl / handleDeleteWidget; identical
175
+ # in pd-openweb). Reading or saving the child sheet directly is
176
+ # refused (verified live 2026-10-08: GetWorksheetControls -> 权限不足,
177
+ # SaveWorksheetControls sourceId=<child> -> code 16). So the index
178
+ # records which parent column hosts each child; field ops write
179
+ # through it (see ``ops._subtable_write``).
180
+ def _subtable_map(self) -> dict[str, dict]:
181
+ if self._subtables is not None:
182
+ return self._subtables
183
+ nav_ids = {item.get("id") for _sid, item in self._ws_items()}
184
+ found: dict[str, dict] = {}
185
+ for _sid, item in self._ws_items():
186
+ parent_id = item.get("id")
187
+ try:
188
+ controls = self.controls(parent_id)
189
+ except Exception as exc: # noqa: BLE001 — one unreadable sheet
190
+ logger.warning("could not read controls of %s while looking "
191
+ "for sub-table sheets: %s", parent_id, exc)
192
+ continue
193
+ for c in controls:
194
+ if c.get("type") != 34:
195
+ continue
196
+ child = (c.get("dataSource") or "").strip("$")
197
+ # A sub-table showing a worksheet that is itself in
198
+ # navigation resolves there; a placeholder (dashed uuid)
199
+ # has no sheet yet.
200
+ if not child or child in nav_ids or "-" in child:
201
+ continue
202
+ found.setdefault(child, {
203
+ "parent": parent_id,
204
+ "parent_name": item.get("name") or "",
205
+ "column": c.get("controlId") or "",
206
+ "column_name": c.get("controlName") or "",
207
+ })
208
+ self._subtables = found
209
+ return found
210
+
211
+ def _subtable_sheet(self, ref: str) -> str:
212
+ """Resolve ``ref`` to an inline sub-table sheet id, or "".
213
+
214
+ Accepts the sheet id, ``<parent>/<sub-table column>`` (each side
215
+ by name or id), or the bare column name when only one parent has
216
+ a sub-table column of that name.
217
+ """
218
+ subs = self._subtable_map()
219
+ if not subs:
220
+ return ""
221
+ hit = ""
222
+ if ref in subs:
223
+ hit = ref
224
+ elif "/" in ref:
225
+ parent_ref, col_ref = ref.rsplit("/", 1)
226
+ for child, h in subs.items():
227
+ if parent_ref in (h["parent"], h["parent_name"]) \
228
+ and col_ref in (h["column"], h["column_name"]):
229
+ hit = child
230
+ break
231
+ else:
232
+ named = [child for child, h in subs.items()
233
+ if ref == h["column_name"]]
234
+ if len(named) > 1:
235
+ where = ", ".join(f"'{subs[c]['parent_name']}/{ref}'"
236
+ for c in named)
237
+ raise ResolveError(
238
+ f"worksheet {ref!r} is ambiguous: more than one "
239
+ f"sub-table has that name — write {where}")
240
+ hit = named[0] if named else ""
241
+ if hit:
242
+ self._check_inline(hit)
243
+ return hit
244
+
245
+ def _check_inline(self, child: str) -> None:
246
+ """Refuse a sub-table that shows a worksheet from another app.
247
+
248
+ Its columns belong to that worksheet, which the parent's
249
+ write-back cannot change — the product only edits a sub-table's
250
+ columns when the sheet was created for it (GetWorksheetInfo
251
+ ``type`` 2; MDPublic sub_list.jsx: ``res.type === 2 ? 'new' :
252
+ 'relate'``).
253
+ """
254
+ if child not in self._subtable_kind:
255
+ try:
256
+ info = ws_mod.get_worksheet_info(self.session, child)
257
+ except Exception as exc: # noqa: BLE001
258
+ logger.warning("could not read sub-table sheet %s: %s",
259
+ child, exc)
260
+ info = {}
261
+ payload = info.get("data") if isinstance(info, dict) \
262
+ and isinstance(info.get("data"), dict) else info
263
+ self._subtable_kind[child] = (payload or {}).get("type") \
264
+ if isinstance(payload, dict) else None
265
+ if self._subtable_kind[child] != ws_mod.SUBTABLE_WORKSHEET_TYPE:
266
+ h = self._subtables[child]
267
+ raise ResolveError(
268
+ f"worksheet {child!r} is shown by the sub-table "
269
+ f"'{h['parent_name']}/{h['column_name']}' but belongs to "
270
+ f"another app; edit its fields in that app")
271
+
272
+ def subtable_host(self, ref: str) -> Optional[tuple[str, str]]:
273
+ """``(parent worksheet id, sub-table column id)`` when ``ref`` is
274
+ an inline sub-table sheet, else ``None``."""
275
+ ws_id = self.worksheet_id(ref)
276
+ for _sid, item in self._ws_items():
277
+ if item.get("id") == ws_id:
278
+ return None
279
+ h = self._subtable_map().get(ws_id)
280
+ return (h["parent"], h["column"]) if h else None
281
+
150
282
  def worksheet_exists(self, ref: str) -> bool:
151
283
  try:
152
284
  self.worksheet_id(ref)
@@ -165,7 +297,11 @@ class AppIndex:
165
297
  def controls(self, ws_ref: str) -> list[dict]:
166
298
  ws_id = self.worksheet_id(ws_ref)
167
299
  if ws_id not in self._ws_controls:
168
- data = ws_mod.get_worksheet_controls(self.session, ws_id, self.app_id)
300
+ host = self.subtable_host(ws_id)
301
+ # A sub-table's sheet is only readable with its parent named.
302
+ data = ws_mod.get_worksheet_controls(
303
+ self.session, ws_id, self.app_id,
304
+ parent_worksheet_id=host[0] if host else "")
169
305
  self._ws_controls[ws_id] = data if isinstance(data, list) else []
170
306
  return self._ws_controls[ws_id]
171
307
 
@@ -1141,8 +1141,11 @@ def _enforce_rules(
1141
1141
  return None
1142
1142
  # Read-only / required are the form's own checks, not business rules;
1143
1143
  # they live in their own module so the two can be worked on apart.
1144
+ # The cells filled in above are the form's, not the caller's: they
1145
+ # go in even when hidden, so only what the caller wrote is judged.
1144
1146
  record_form_checks.check_form(session, worksheet_id, controls,
1145
- row_id=row_id, app_id=app_id)
1147
+ row_id=row_id, app_id=app_id,
1148
+ defaulted_ids=defaulted)
1146
1149
  check = record_rules.check_pending_write(
1147
1150
  session, worksheet_id, controls, row_id=row_id, app_id=app_id,
1148
1151
  defaulted_ids=defaulted)
@@ -1179,7 +1182,8 @@ def _enforce_rules_for_batch(
1179
1182
  # Same form checks as the single-row path, judged before any row is
1180
1183
  # written — see _enforce_rules.
1181
1184
  record_form_checks.check_form_batch(session, worksheet_id, items,
1182
- app_id=app_id)
1185
+ app_id=app_id,
1186
+ defaulted_ids=defaulted)
1183
1187
  rules, controls = record_rules.load_rules_and_fields(
1184
1188
  session, worksheet_id, app_id)
1185
1189
  merged = record_rules.RuleCheck()