Skip to content

contributing: link to rename-safety contract#57

Merged
dadachi merged 1 commit intomainfrom
contributing-rename-safety
May 10, 2026
Merged

contributing: link to rename-safety contract#57
dadachi merged 1 commit intomainfrom
contributing-rename-safety

Conversation

@dadachi
Copy link
Copy Markdown
Contributor

@dadachi dadachi commented May 8, 2026

Summary

  • Add a new ## Rename safety section to CONTRIBUTING.md that links to the canonical SUBSTRATE-CONTRACT.md in the agent repo.
  • Includes a quick rule of thumb (avoid a/an before Shop/Shopkeeper/ItemTag) and Rails-flavored failure-mode examples (OpenAPI summary "Get an item tag""Get an patient"; test descriptor test "destroy deletes an item_tag""destroy deletes an patient").
  • Placed after the Pull Requests group (Style/Tests subsections) and before Development Setup, mirroring the placement in the iOS PR.

Why

The agent at nativeapptemplate-agent mechanically renames domain words across all case forms when generating customized projects. Without a link from the substrate's natural contributor onboarding path, the contract doc only reaches contributors who already know to look in the agent repo.

Mirrors the section added to the iOS substrate in NativeAppTemplate-Free-iOS#67.

Test plan

  • CONTRIBUTING.md renders correctly in GitHub markdown preview
  • Links resolve to the agent repo's SUBSTRATE-CONTRACT.md

🤖 Generated with Claude Code

Add a Rename safety section to CONTRIBUTING.md linking to the canonical
SUBSTRATE-CONTRACT.md in the agent repo. Includes a quick rule of thumb
(avoid "a" / "an" before Shop / Shopkeeper / ItemTag) and a Rails-flavored
failure-mode example (OpenAPI summaries and test descriptors).

Mirrors the section added to the iOS substrate in
nativeapptemplate/NativeAppTemplate-Free-iOS#67.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dadachi added a commit that referenced this pull request May 10, 2026
Reverses the previous "Number → name" / "is ready" decision once it
became clear that any state-verb baked into the substrate's notifier
title or class name fights the agent's domain-adapt step. The agent
extends/renames the AASM state machine per spec (idled/completed →
e.g. waiting/seated for restaurant, pending/seen for vet clinic), but
its rename plan only handles the four model-level tokens (Shop /
Shopkeeper / ItemTag / NativeAppTemplate). State names cascading into
notifier file/class/locale-key/title are out of scope for the
rename-safety contract (#57).

So the substrate's notifier ships state-verb-free:

- File:        item_tag_called_notifier.rb → item_tag_notifier.rb
- Class:       ItemTagCalledNotifier       → ItemTagNotifier
- Locale key:  notifiers.item_tag_called   → notifiers.item_tag
- Title:       "%{name} is ready"          → "%{name}"
- Body:        "Please proceed to %{shop}." → "%{shop}"

`ItemTag` itself IS in the rename plan, so file/class/locale-key
cascade through `item_tag → patient/reservation/todo` cleanly.
`%{name}` and `%{shop}` are interpolation keys, not renameable
tokens. Result: substrate copy survives any state-verb rewrite the
agent's adapt step does, at the cost of vague substrate copy. The
adapt step can rewrite richer per-domain copy when it wants.

Tests + rubocop clean.
dadachi added a commit that referenced this pull request May 10, 2026
* Add push notifications scaffolding via noticed v2

PR #1 of 5 in #58. Scaffolds the Rails-side groundwork for native push
notifications. Provider integration (APNs + FCM) and ItemTag AASM
wiring follow in PR #2 once APNs .p8 and FCM service-account JSON are
provisioned. Client work (free + paid iOS/Android) follows in PRs #3-5.

What lands here
- noticed v2 gem + the two engine migrations (Noticed::Event,
  Noticed::Notification, both UUID-keyed to match this substrate's
  primary_key_type)
- Device model + migration: shopkeeper-scoped, unique on
  [platform, token], last_active_at for staleness scoping; ios/android
  enum
- Api::V1::Shopkeeper::DevicesController:
    POST /api/v1/shopkeeper/devices  — idempotent upsert (rebinds token
                                       to current_shopkeeper if it
                                       previously belonged to someone
                                       else, e.g. shared device after
                                       sign-out/sign-in); 201 on
                                       create, 200 on touch
    DELETE /api/v1/shopkeeper/devices/:id — unregister (404 on someone
                                            else's device, scoped via
                                            current_shopkeeper.devices)
- DevicePolicy + DeviceSerializer following existing substrate
  conventions (BasePolicy + JSONAPI::Serializer)
- ApplicationNotifier base + example ItemTagCalledNotifier (no
  delivery methods wired yet — title/body/url are i18n-resolved via
  notification_methods so PR #2 just needs to add deliver_by :ios +
  :android and trigger from ItemTag's AASM complete event)
- Shopkeeper.has_many :devices (dependent: :destroy) and
  :notifications (as: :recipient, class: Noticed::Notification)
- Locale entries under notifiers.item_tag_called

Tests: 21 new runs (Device model 9, DevicesController 8, notifier 4),
0 failures. Full suite now 419 runs / 868 assertions / 0 failures /
0 errors / 0 skips. rubocop clean (239 files, 0 offenses).

* Wire deliver_by :action_push_native via Rails-native action_push_native

Install action_push_native 0.3.x and generate ApplicationPushNotification,
ApplicationPushDevice, ApplicationPushNotificationJob, and config/push.yml.
Add deliver_by :action_push_native to ItemTagCalledNotifier so push
notifications route through Rails 8.1's Action Push Native (single
abstraction over APNs + FCM) instead of the Noticed gem's per-platform
:ios / :fcm deliverers.

APNs/FCM credentials remain placeholders in config/push.yml — provision
via bin/rails credentials:edit before enabling delivery. Bridging the
existing Device registration API to ApplicationPushDevice (so registered
tokens actually flow into Action Push Native delivery) is a follow-up.

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

* Generalize notifier copy: %{number} → %{name}, drop "Number" prefix

Substrate post-Phase-1 (#45) is generic single-resource CRUD, not
queue-only — `ItemTag.name` can be a queue number ("A001"), a pet
name ("Mittens"), a task title, etc. The copy "Number %{number} is
up" only reads correctly for the queue case, and the agent's renamer
doesn't substitute the word "Number" (it's not in the rename plan),
so the wrong copy ships to every renamed app.

Change to "%{name} is ready" — generalizes cleanly across the queue,
reservation, vet-clinic, and task-tracker domains the substrate
targets. Body unchanged.

Test passes; rubocop clean.

* Generalize notifier: drop "Called" + state-verb copy (rename-resistant)

Reverses the previous "Number → name" / "is ready" decision once it
became clear that any state-verb baked into the substrate's notifier
title or class name fights the agent's domain-adapt step. The agent
extends/renames the AASM state machine per spec (idled/completed →
e.g. waiting/seated for restaurant, pending/seen for vet clinic), but
its rename plan only handles the four model-level tokens (Shop /
Shopkeeper / ItemTag / NativeAppTemplate). State names cascading into
notifier file/class/locale-key/title are out of scope for the
rename-safety contract (#57).

So the substrate's notifier ships state-verb-free:

- File:        item_tag_called_notifier.rb → item_tag_notifier.rb
- Class:       ItemTagCalledNotifier       → ItemTagNotifier
- Locale key:  notifiers.item_tag_called   → notifiers.item_tag
- Title:       "%{name} is ready"          → "%{name}"
- Body:        "Please proceed to %{shop}." → "%{shop}"

`ItemTag` itself IS in the rename plan, so file/class/locale-key
cascade through `item_tag → patient/reservation/todo` cleanly.
`%{name}` and `%{shop}` are interpolation keys, not renameable
tokens. Result: substrate copy survives any state-verb rewrite the
agent's adapt step does, at the cost of vague substrate copy. The
adapt step can rewrite richer per-domain copy when it wants.

Tests + rubocop clean.

* Swap notifier title/body: shop in title, item name in body

Push-notification UX convention is source-in-title, event-in-body —
WhatsApp (sender → message), Slack (channel → message), Calendar
(event → location). Shop is the recognizable persistent entity that
anchors the notification; item name is variable per-event content.

Title: %{name} → %{shop}
Body:  %{shop} → %{name}

Tests + rubocop clean.

* credentials.yml.tt: add action_push_native APNs + FCM placeholders

config/push.yml looks up Rails.application.credentials.dig(
:action_push_native, :apns, :key_id) and friends, but the credentials
template that seeds `bin/rails credentials:edit` on first generation
didn't expose those keys. Fresh developers would hit silent nil on
first push delivery without knowing where the lookup expected the
secret.

Adds the same shape Resend's api_key already follows: empty
placeholder under the documented key path. Comment notes which
inputs are needed (APNs key_id + .p8 contents, FCM service-account
JSON).

* push.yml: move team_id/topic/project_id to credentials too

Three deployment-specific values were still hard-coded as placeholders
in config/push.yml:

- apple.team_id    (Apple Developer team identifier — per-deployer)
- apple.topic      (iOS bundle identifier — per-deployment)
- google.project_id (Firebase project identifier — per-deployment)

These don't belong in source. apple.topic in particular is a rename-
pipeline trap: the agent renames the iOS bundle id when generating a
domain-customized variant (com.nativeapptemplate.* → com.<spec>.*),
but the rename pipeline only operates on code/locales/OpenAPI — not
on push.yml strings. So a hard-coded `your.bundle.identifier` here
silently desyncs from the renamed app's actual bundle id and push
delivery breaks with a non-obvious error.

Move all three to Rails.application.credentials.dig(:action_push_native,
...) so they're deploy-time configuration, not source-controlled
state. Add the same fields to the credentials.yml.tt template so
`bin/rails credentials:edit` exposes the expected key paths.

Tests + rubocop clean.

* openapi.yaml: document Device endpoints + schemas

Layer 1 of the agent's reviewer (per the agent's docs/SPEC.md)
checks OpenAPI parity between Rails ↔ iOS networking ↔ Android
repository layers. Adding the Device controller without the
corresponding spec entries means PRs #3-5 (the iOS/Android push
registration clients) wouldn't have a contract to integrate against
and would fail Layer 1 contract-parity scan.

Adds:
- Tag: Devices
- Path POST /devices: idempotent register; 201 on create, 200 on
  touch, 422 on validation error
- Path DELETE /devices/{deviceId}: 204 no_content, 404 if device
  isn't owned by current_shopkeeper
- Schemas: DeviceAttributes, Device, DeviceCreateRequest
  (jsonapi-style envelope to match the rest of the API)

YAML parses; paths now 25, schemas now 38.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dadachi dadachi merged commit 52a5283 into main May 10, 2026
3 checks passed
@dadachi dadachi deleted the contributing-rename-safety branch May 10, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant