Change Log
2026-04-22 — Bug Hunt Round 20 (Card feature pre-submission — 4 fixes)
CardManager.swift (R20-A01 Critical)
saveDraft(): stripeditTokento""before JSON-encoding and writing to UserDefaults. Previously the secret token was persisted in plaintext in the app's Documents/UserDefaults plist, which would be included in device backups (iCloud / iTunes / Time Machine). Only Keychain should hold the token.
CardKeychain.swift (R20-A02)
save(username:editToken:): skipset(tokenAccount, value: editToken)when editToken is empty. Previously an empty token from a transientresolveEditTokenfailure would overwrite the last known-good token in the Keychain, breaking subsequent edits until the user manually re-synced.
CardModel.swift / CardWriteView.swift (R20-A03)
publicURL: returns""instead of"https://.nfc.bz"when username is empty, so share/NFC write callers can bail out.CardWriteView.writeToTagnow guards on empty URL before callingwriter.write. Previously, an edge-case empty-username card would write an invalid URL to the tag.
CardEditorView.swift / CardUsernamePickerView.swift / Localizable.xcstrings (R20-B01)
- Replaced hardcoded
TextField("username", …)placeholder with localization keycard.username.placeholder. Added en ("username") + ja ("ユーザー名") translations.
2026-04-22 — Bug Hunt Round 19 (5-Agent: NFC/Auth/CardUI/StoreKit/History — 4 fixes)
NFCWriter.swift (R19-A01 Critical)
writeToTag: changed callback to[weak self]+guard let self else { session.invalidate(); return }. Previously the callback held a strong implicit self — if the view was dismissed while a write was in flight, the callback would execute on a deallocated object.didInvalidateWithError: added[weak self]+ guard toDispatchQueue.main.asyncblock. Same pattern as above — invalidation callbacks can arrive after NFCWriter deallocates.
HistoryView.swift (R19-B01 Critical)
.onDelete: snapshotvisibleItemsinto a locallet snapshotbefore passing todelete(ids:). PreviouslyvisibleItemswas read inline inside the closure, which is a TOCTOU race — an iCloud merge notification arriving between swipe and delete commit could shift indices and delete wrong items.
AnalysisView.swift (R19-C01 Critical)
RecordFixSheet: added@EnvironmentObject var history: HistoryManager. On write successonChange, now callshistory.add(url: finalValue, type: .write)beforeonWriteSuccess(). Previously, writes from the tag-fix sheet were invisible in history and the widget.
WriteView.swift (R19-D01)
- Added
@State private var lastWrittenURL. Snapshot the URL just beforenfcWriter.write(url:)is called. History now records the snapshotted URL, not the currenturlTextat the time the success message arrives (which could differ if the user edited the field while NFC was scanning).
2026-04-22 — Bug Hunt Round 18 (5-Agent: NFC/StoreKit/TeamMode/ToolForm/Widget — 6 fixes)
ToolFormView.swift (R18-C01 Critical)
performReviewWrite(): addedguard !isCreatingTag else { return }at function entry. Double-tap could consume two review credits before NFC session started, becauseisCreatingTagwas set after the guard checks but still within the same sync frame.
NFCReader.swift (R18-A01)
read(): addedsession?.invalidate(); session = nilbefore creating a newNFCNDEFReaderSession. Double-tap could start two concurrent CoreNFC sessions, violating the single-session contract.
TeamManager.swift (R18-D01)
syncWithServer(): addedsyncInFlightguard (mirrors CardManager pattern). Rapid foreground-entry notifications could spawn concurrent sync calls, causing interleavedmembersarray mutations.
ToolFormView.swift (R18-C02)
- Removed empty
onChange(of: nfcWriter.isWriting || nfcWriter.isShortening)block. Dead code triggered unnecessary SwiftUI reactivity on every write-state change.
ToolFormHelpers.swift (R18-B01)
geocodeAddress(): added stale-result guard in completion block — checksinput1hasn't changed before applying geocode result. Rapid address input previously allowed earlier slower geocode responses to overwrite the current correct result.
SettingsView.swift / Localizable.xcstrings (R18-D02 Critical)
- Replaced hardcoded Japanese strings
"サインインして機能を有効化","分析・タグ管理・デバイス間同期", and"管理ダッシュボード"with localization keyssettings.account.signInPromptTitle,settings.account.signInPromptSubtitle, andsettings.admin.dashboard. Added en + ja translations to Localizable.xcstrings.
2026-04-22 — Bug Hunt Round 17 (5-Agent: Concurrency/UX/Architecture/Edge Cases/Integration — 16 fixes)
WriteEventUploader.swift (R17-A21 Critical)
flushQueue: snapshot queue before loop; trackfailedIDs; only remove events whose upload succeeded. Previously the queue was drained upfront, causing permanent event loss on network failure.
NFCWriter.swift (R17-A22)
verifyAndFinishTask: addedguard !Task.isCancelled else { session.invalidate(); return }after 300ms sleep. Prevents writes to sessions already invalidated bycancelSession()or scenePhase background.
AuthManager.swift (R17-A23)
Keychain.setData: added#if DEBUGlog whenSecItemAddreturns non-zero status. Previously all Keychain write failures were silent.
CardManager.swift (R17-A24, R17-D14)
update(): addedguard !isBusyto prevent concurrent mutations overwriting each other's snapshots.refreshCard(): when server returns nil (card deleted), clearsself.card, Keychain, UserDefaults, and KVS — transitions to NoCardView instead of showing stale ghost card.
CardEditorView.swift (R17-B15)
lastNameKanaUserEdited/firstNameKanaUserEdited: addedonChangeon kanji fields to reset flag when field is cleared. Previously clearing the kanji field left the kana flag locked, permanently disabling auto-fill.
ToolFormComponents.swift (R17-B17)
PhoneFormField: removed.onAppear { showKeyboard = true }. Keyboard was re-triggered on every tab switch back to a form containing a phone field.
CardPreviewView.swift (R17-B19)
ForEachon contact and social rows: changedid: \.labeltoid: \.offset(viaenumerated()). Duplicate label strings (e.g. two websites) caused SwiftUI identity collision and potential crash.
SettingsView.swift (R17-B21)
- Sign-in prompt: added
@State private var hasShownSignInPrompt = falseguard. Prompt now fires at most once per session instead of on every tab re-selection.
TeamManager.swift (R17-C13, R17-C21, R17-D18)
removeMember: addedguard !isBusy+isBusy = true/defer isBusy = false. Double-tap could delete the same member twice.addMember: added pre-flight member limit check viamemberLimitparameter. Server call no longer attempted when limit already reached.syncWithServer: catchesCardClientError.notFoundspecifically and callsreset()to clear ghost team UI when the team is deleted server-side.
UserDefaultsKeys.swift / NFCTagLabelSync.swift (R17-C15)
- Added
UDKey.labelSyncLastRun = "label_sync_last_run_at". UpdatedNFCTagLabelSyncto useUDKey.labelSyncLastRuninstead of the hardcoded string.
StoreManager.swift (R17-D21)
refreshEntitlements(): added trial elapsed-time re-evaluation. PreviouslyisTrialActivewas computed only ininit()— a trial that expired mid-session would not fliphasProAccessto false until next launch.
NFCCopier.swift (R17-D22)
- Empty source tag path: moved
self.state = .failed(...)to beforesession.invalidate()(viaDispatchQueue.main.async). UI now reflects the error before the NFC sheet dismisses.
OCRScannerView.swift (R17-D16)
dataScanner(_:didTapOn:): addedguard !recognized.isEmpty else { return }before dismissing scanner. Empty OCR transcripts no longer wipe the bound text field.
2026-04-22 — Bug Hunt Round 16 (5-Agent: Runtime/UX/Design/QA/Integration — 11 fixes)
CardModel.swift (R16-C04)
- Added
MyCard.encodeForAPI()as single canonical encoder for all API clients. BothSupabaseCardClient.encodeForInsertandTeamClient.encodeCardnow delegate to this method. Eliminates field-drift risk when newMyCardproperties are added.
StoreManager.swift (R16-A18)
syncTransactionToNfcBzcatch block: demoted error fromlastErrorMessage = ...to#if DEBUG print(...). Background sync failure no longer surfaces as a purchase-failure alert after a successful IAP.
NFCWriter.swift (R16-A16)
verifyAndFinish: capturedisClearOperationintolet wasClearand reset it before the 300msTask.sleep. Eliminates data race where a concurrentdidInvalidateWithErrorreset could corrupt the flag read inside the CoreNFC callback.
WriteView.swift / SearchWriteView.swift (R16-D03)
- Added
@Environment(\.scenePhase)andonChange(of: scenePhase) { phase == .background { nfcWriter.cancelSession() } }. Matches the existingToolFormViewpattern. PreventsisWritingstuck true when iOS silently drops the NFC session on app background.
SimpleWriteView.swift (R16-B05)
- History dropdown rewrite button: added
.disabled(nfcWriter.isWriting || nfcWriter.isShortening)and gray background while writing. Prevents concurrent NFC session from history during an active write.
PaywallView.swift (R16-B07, R16-B13)
proLoadingSpinner: finalelsebranch replacedProgressView()with an error icon (exclamationmark.triangle). Covers the "StoreKit loaded but product list empty" state that previously showed an infinite spinner.CreditPackCardoverlay stroke: changed fromTheme.accent(same as filled background) toColor.white.opacity(0.5)when selected. Selection ring is now visible.
HistoryView.swift (R16-B10)
- Trash toolbar button guard:
!history.items.isEmpty→!visibleItems.isEmpty. Button no longer appears when only tap history exists with an empty write list.
CardManager.swift (R16-D01)
suggestUsername(maxAttempts:): changedfor i in 2...maxAttemptstofor i in 2...max(2, maxAttempts). Prevents fatalClosedRangecrash when caller passesmaxAttempts < 2.
UserDefaultsKeys.swift + ToolFormView.swift + ToolFormHelpers.swift (R16-C01, R16-C02)
- Added
UDKey.reviewSearchHistory,UDKey.shortcutNameHistory,UDKey.toolFormStorage(_:). - Changed
ToolFormView.reviewHistoryKeyandshortcutHistoryKeyfrom bare strings / instancelettostatic letreferencing the enum constants. - All call sites updated to
Self.shortcutHistoryKey/UDKey.*.
SupabaseCardClient.swift (R16-C05)
fetchEditToken: replaced hardcoded"https://krbkqkqpxxjdboqxfhyj.supabase.co/functions/v1/card-claim"withSupabaseClient.url.absoluteString + "/functions/v1/card-claim". Single source of truth for the Supabase project URL.
CardModeRootView.swift (R16-A20)
create(): removed redundantsaving = trueinside the async function body. The caller's pre-Tasksaving = trueis the canonical guard;defer { saving = false }handles cleanup.
CardUsernamePickerView.swift (R16-D12)
- Added
CheckState.networkErrorcase. WhencheckUsernameAvailablereturnsnil(offline), the picker now shows a "Cannot verify" icon and allows the user to proceed (server validates uniqueness on insert with a 409 response). Previously the user was silently blocked with no explanation.
ToolFormHelpers.swift (R16-C08)
- Removed
_ = itemno-op suppression. Changedvar itemtolet itemto clarify that the value is not mutated after construction.
2026-04-22 — Bug Hunt Round 15 (5-Agent Deep Audit: NFC/Async/Memory)
NFCTool iOS — R15: 11 fixes across retain cycles, session safety, async cancellation
NFCAnalyzer.swift + NFCWriter.swift (R15-01)
- NFC delegate closures (
session.connect,tag.queryNDEFStatus,tag.readNDEF) changed to[weak self]capture withguard let selffallback. Prevents retain cycle betweenNFCNDEFReaderSessionand its delegate that prevented deallocation across repeated scans.
NFCWriter.swift (R15-02, R15-14)
verifyAndFinish: replacedDispatchQueue.global().asyncAfterwithTask { [weak self] in try? await Task.sleep(for: .milliseconds(300)) }. Eliminates @Published mutation from background thread.cancelSession(): now resetssessionAlertMessageto default. Prevents "Hold near tag to lock" prompt appearing on the next URL write session.
CardModeRootView.swift (R15-03)
CreateCardFlow: added.onDisappear { checkTask?.cancel() }. Username availability check no longer runs after the view is dismissed.
TeamMemberDetailView.swift (R15-05)
openEditInBrowser(): addedguard !card.editToken.isEmptybefore constructing URL. Prevents opening Safari with an auth-less?t=URL.
NFCCopier.swift (R15-08)
startWritingDestination(): addedsession?.invalidate(); session = nilbefore creating the new session. Prevents two concurrent NFC sessions on rapid Retry taps.
StoreManager.swift (R15-10)
- Pro/lifetime purchase path: 3 bonus credits now gated behind dedup check using
UDKey.grantedTxIDswithpro_bonus_prefixed key. Prevents double-grant on sandbox restore or re-delivery.
PaywallView.swift (R15-11)
- Added
.onChange(of: store.isLiveTagSubscriber)and.onChange(of: store.isTeam)dismiss triggers alongside existingisProtrigger.
HistoryManager.swift (R15-12)
- Extracted
DateFormatterfrom inline closure insave()tolazy var widgetDateFormatter. Eliminates repeated formatter allocation on every save call.
FriendCardView.swift (R15-15)
writeSingle(_:): addedguard !(nfcWriter.isWriting || nfcWriter.isShortening)at entry. Menu items can no longer bypass the.disabled()modifier to open a second NFC session.
SearchWriteView.swift (R15-17)
- Added
@State private var searchTask: Task<Void, Never>?.runSearch()stores the task and cancels the previous one..onDisappear { searchTask?.cancel() }prevents stale results from being applied after dismiss.
CardManager.swift (R15-18)
kvsObserver: NSObjectProtocol?property added.NotificationCenter.addObserver(forName:…)result now stored.deinitcallsremoveObserver(kvsObserver). Prevents dangling observer accumulation.
Side-effect checks
NFCAnalyzerreadNDEF closure: addedguard let selfbefore existingself.references — all paths handled.NFCWriter.verifyAndFinishTask:[weak self]checked for all inner closures includinglockTagcall path.StoreManagerPro bonus dedup: prefixpro_bonus_ensures no collision with consumable transaction IDs in same array.HistoryManager.widgetDateFormatter:lazy varon@MainActorclass — thread-safe by actor isolation.SearchWriteView.searchTaskcancel:guard !Task.isCancelledadded before bothresultsanderrorTextmutations.
2026-04-22 — Bug Hunt Round 14 (5-Agent Deep Audit)
NFCTool iOS — R14: 9 fixes across security/runtime/UX/maintainability
ContentView.swift (R14-02)
- Removed inline admin email array from
.onChange(of: auth.user?.email). Now usesauth.isAdminfromAdminManageras single source of truth.
ToolFormView.swift (R14-03, R14-20)
- Removed dead
if false && !previewURI.isEmpty && isValidpreview block (26 lines of dead code). - Simplified
.foregroundStyle(store.reviewCredits > 0 ? .white : .white)to.foregroundStyle(.white)(dead ternary).
StoreManager.swift (R14-11, R14-26)
- Fixed
observeTransactionUpdates()credit path:await transaction.finish()now called AFTERaddCredits()(consistent withpurchase()path). Prevents potential credit loss on process kill between finish and addCredits. - Fixed credit dedup set trimming in both
purchase()andobserveTransactionUpdates(): changed from non-deterministicSet→Array.suffixto ordered[String]array withappend+suffix. Prevents old transaction IDs from being preserved over recent ones.
SettingsView.swift (R14-12, R14-19)
- Wrapped debug diagnostic string in
labelSyncResultMessagewith#if DEBUG. Production users no longer see"認証:true token:true"in the label sync alert. - Removed two
UserDefaults.standard.synchronize()calls (deprecated since iOS 12, unnecessary).
AnalysisView.swift (R14-13, R14-23)
- Removed
onAppearauto-trigger ofanalyzer.analyze(). NFC scanning now requires explicit user tap (the existing Analyze button). - Fixed
navigationTitlefrom"sns.navigationTitle"to"analysis.navigationTitle".
CardModeRootView.swift (R14-09)
CreateCardFlow:saving = truenow set synchronously in button action beforeTask { await create() }, eliminating the rapid double-tap window.
WriteEventUploader.swift (R14-22)
- Changed
enqueue()from fire-then-queue-on-failure to queue-first-then-upload. AddedremoveFromQueue(_ clientEventId:)helper. Process kill during upload no longer loses the write event.
HistoryView.swift (R14-24)
visibleItemsnow filters to.writetype items only, matching the"history.section.writes"section header. Read-type items no longer appear in the writes section.
Side-effect checks
StoreManagerdedup: bothpurchase()andobserveTransactionUpdates()now use same ordered-array pattern. Verified no third dedup site exists.WriteEventUploader.flushQueue(): usespendingQueuedirectly; compatible with new remove-on-success pattern since both are@MainActor.AnalysisViewauto-scan removal:RecordFixSheet.onWriteSuccessstill callsanalyzer.analyze()(intentional re-scan after fix).HistoryViewwrite filter:history.section.tapssection unaffected (uses separateTapHistoryView).
2026-04-21 — Bug Hunt Round 11 (Multi-Agent Full Scan)
NFCTool iOS — R11: navigation titles, i18n, safety, continuation leak
ToolsView.swift / WiFiSavedListView.swift / WriteView.swift
- B-14/B-15/B-16:
.navigationTitle("sns.navigationTitle")→ 各画面の正しいキーに修正
BusinessRootView.swift
- B-17:
.navigationTitle→"mode.business.title"キー化 - B-03: placeholder CompanyEntry
"公式サイト"→"business.entry.defaultName"ローカライズキー化
CompanyWriteView.swift
- B-18:
.navigationTitle("sns.navigationTitle")→"mode.business.title"キー化
PaywallView.swift
- B-13:
Text("NFC SNS CARD MAKER Pro")→Text("paywall.hero.title")ローカライズキー化
ToolFormHelpers.swift
- C-02:
URLComponents(string:)!force unwrap → safe guard + fallback
CardMode/CardEditorView.swift
- D-05:
withCheckedContinuation→withTaskCancellationHandler { await withCheckedContinuation {...} }でキャンセル時のリーク防止 OneShotLocationDelegate.forceResolve()追加
CardMode/CardKeychain.swift
- A-16:
SecItemAdd失敗時に#if DEBUGログ追加
Localizable.xcstrings
- Added:
business.entry.defaultName(15言語)
2026-04-21 — Bug Hunt Round 10 (Multi-Agent Full Scan)
NFCTool iOS — R10: i18n hardcodes, timeouts, safety, UX
SettingsView.swift
- B-05: 「管理ダッシュボード」→
settings.dashboard.linkローカライズキー化 (15言語)
CardMode/CardModeRootView.swift
- B-02: TextField placeholder "nextcode" →
card.create.userId.placeholderキー化
CardMode/CardManager.swift
- A-08:
suggestUsername—20 - String(i).countが負になる問題をmax(0, ...)で修正 - A-07:
refreshCard失敗時に非ネットワークエラーをlastErrorに設定
CardMode/SupabaseCardClient.swift
- D-17: 全URLRequest に
timeoutInterval = 15追加(60秒デフォルト→15秒)
PlaceSearchView.swift
- D-04: クエリロード後に10秒タイムアウトをセット。JSが応答しない場合に
onFailed()を呼び出しUIをアンブロック
NFCTagLabelSync.swift
- D-13: タイムアウト/デコードエラーで正確なメッセージを渡すよう修正
TapHistoryView.swift
- B-11: 「読み込み中...」「まだタップされていません」→ ローカライズキー化
- D-11: 日付パース失敗時にDEBUGログを出力(サイレントデータロスを可視化)
TeamMode/TeamModeRootView.swift
- B-13: メンバーグリッドセルを
onTapGestureからButton + UIImpactFeedbackGeneratorに変更
TeamMode/TeamManager.swift
- A-09: memberTokens Keychainデコード失敗時に不正データを削除+DEBUGログ出力
Localizable.xcstrings
- Added:
settings.dashboard.link,card.create.userId.placeholder,common.loading,tapHistory.emptyState(各15言語)
2026-04-21 — Bug Hunt Round 9 (Final Edge Case Pass)
NFCTool iOS — R9: Timer race, re-entrancy, empty token guard
NFCWriter.swift
- R9-D-03: Cancel
errorClearTaskat start of success path inverifyAndFinish()— prevents stale 6s timer from erasing success banner after fail→succeed sequence
CopyFlowView.swift
- R9-D-04:
.sourceReadretry button now has.disabled(isBusy)+primaryButtonStyle(isEnabled: !isBusy)— prevents double-tap while session is active
HistoryManager.swift
- R9-D-08: Added
isSavingre-entrancy guard tosave()— prevents duplicate write if iCloud notification fires during an in-progress save
StoreManager.swift
- R9-D-07: IAP sync now guards
!accessToken.isEmptybefore making network call — prevents sendingBearerwith empty token
2026-04-21 — Bug Hunt Round 8 (Final Sweep)
NFCTool iOS — R8: Widget i18n fix
NFCToolApp.swift
- R8-02: Widget recent-tap time display replaced hardcoded Japanese strings with
RelativeDateTimeFormatter— all device languages now render correctly
2026-04-21 — Bug Hunt Round 7
NFCTool iOS — Multi-Agent R7 Audit: Thread Safety, UX Accuracy, Edge Cases
TapHistoryView.swift
- R7-A-01: HTTP error handling now distinguishes 401/403/5xx with specific localized messages
- R7-B-xx:
relativeTimeandformattedTimereplaced hardcoded Japanese strings withRelativeDateTimeFormatterand systemDateFormatter— all languages now display correctly
WiFiPhotoPicker.swift
- R7-B-03:
.onAppearcamera auto-open now guarded (capturedImage == nil && !showImagePicker) — prevents camera reopening every time image picker dismisses
LocationManager.swift
- R7-D-02: Added
isRequestingflag to prevent re-entrantrequestOnce()calls — all exit paths (success/fail/denied) reset the flag
SNSGridEditorView.swift
- R7-C-01: Pro gate moved from "Done" button to individual edit operations (move/delete/add/reset) — prevents free users from making changes that persist without a paywall gate
SNSIDHistory.swift
- R7-thread: Added
@MainActorannotation;cloudStoreDidChangeconverted to@objc nonisolatedwithTask { @MainActor }to match Swift concurrency model
SNSGridOrder.swift
- R7-thread: Same
@MainActor+@objc nonisolatedpattern applied (same class, same issue)
NFCShortener.swift
- R7-D-04: Added URL length guard (
target.count <= 2048) before POST - R7-D-04: Added
decoded.id.isEmptyguard with descriptive error when server returns empty short ID
Localizable.xcstrings
- Added
tapHistory.error.signInRequired,authExpired,forbidden,serverError,fetchFailedkeys
2026-04-21 — Bug Hunt Round 6
NFCTool iOS — Multi-Agent R6 Audit: Session Safety, State Correctness, UX, Widget
NFCAnalyzer.swift
- R6-D-06: Added
guard !isReading else { return }at top ofanalyze()to prevent double-session creation on rapid tap
CopyFlowView.swift
- R6-D-15:
.idlecase button now applies.disabled(isBusy)andprimaryButtonStyle(isEnabled: !isBusy)—isBusywas defined but never wired to the button
WidgetSharedData.swift
- R6-D-11:
updateSNSLinksnow usesguardwith fallback print instead of silent skip — failure is logged;reloadWidgets()is never called on invalid data (was: might call after silent nil)
HistoryManager.swift
- R6-C-07: Cloud trim loop now drops 20% (1/5) per iteration instead of 10% (1/10) — guarantees convergence even for items approaching 180 KB each
- R6-D-13:
mergeItemsnow resolves UUID conflicts by timestamp (newer date wins) instead of always preferring local; return early if no cloud additions detected
HistoryView.swift
- R6-B-09 (deepen): Replaced
overlay(alignment:.bottom)withsafeAreaInset(edge:.bottom)for ResultBanner — banner now renders above tab bar via safe area rather than fixed padding
PaywallView.swift
- R6-B-05: Product loading area now distinguishes 3 states:
productsLoading(ProgressView),lastErrorMessage != nil(error icon + message), and initial (ProgressView) — was always showing ProgressView
Localizable.xcstrings
- Added
paywall.loadingFailedkey in 15 languages
2026-04-21 — Bug Hunt Round 5
NFCTool iOS — Security + Write Accuracy + UI Audit
BusinessGroupDetailView.swift
- R5-A-01: Team edit token migrated from UserDefaults (plaintext) to Keychain; legacy migration on first launch
- R5-C-02: Removed dead
creditWasDeductedstate variable
SimpleWriteView.swift
- R5-A-02: Added
lastWrittenURLstate;onChange(of: message)now records the actual written URL instead offullURL(current UI state), fixing wrong URL in history on quick-rewrites from the history dropdown - R5-D-01: Quick-rewrite button in history dropdown now checks
store.hasProAccessbefore writing
AnalysisView.swift (RecordFixSheet)
- R5-A-05: Write button label shows ProgressView during
isShortening(was onlyisWriting)
HistoryView.swift
- R5-B-01:
HistoryRow.isWritingnow includesnfcWriter.isShortening— prevents double-tap during URL shortening - R5-C-03: Section titles use
String(localized:)keyshistory.section.writesandhistory.section.taps
FriendCardView.swift
- R5-B-02:
navigationTitlecorrected to"friendCard.navigationTitle"
AnalysisView.swift
- R5-B-03:
navigationTitlecorrected to"analysis.navigationTitle"(key was already defined)
ToolFormView.swift
- R5-B-04:
.urltool now callsnfcWriter.write(url:)instead ofwriteURI()so http(s) URLs go through the nfc.bz shortener (tap tracking + remote update)
Localizable.xcstrings
- Added
friendCard.navigationTitle(ja/en) - Added
history.section.writes(ja/en) - Added
history.section.taps(ja/en)
2026-04-21 — Write Counting Full Audit (Round 4)
NFCTool iOS — All Write Paths Verified
SearchWriteView.swift
- NEW-01:
onChange(of: isSuccess)→onChange(of: message)for consecutive write reliability - NEW-01:
shortenAs: .review→shortenAs: .url(search results are generic URLs, not review tags) historyManager.add()+ widget refresh now fires on every successful write
FriendCardView.swift
- NEW-02: Was completely missing
history.add,onChange, disabled guard, and shortener path - http(s) URLs now route through
write(url:)(shortener); other schemes usewriteURI() - Added
onChange(of: nfcWriter.message)recording history; added disabled guard
CardWriteView.swift
- NEW-03: Added
history.add(url:type:label:kind:"card")viaonChange(of: writer.message) - Added write-in-progress UI (ProgressView spinner, gray button) + disabled guard
TeamMemberDetailView.swift
- NEW-04: Added
history.add(url:type:label:kind:"team")viaonChange(of: writer.message) - Added write-in-progress UI + disabled guard
AnalysisView.swift (RecordFixSheet)
- NEW-05:
writer.writeURI(finalValue)→ conditional:write(url:)for http(s),writeURI()for other schemes - NEW-05:
onChange(of: writer.isSuccess)→onChange(of: writer.message)
2026-04-21 — Bug Hunt Round 3
NFCTool iOS
AuthManager.swift
- A-11:
restoreSessionreplaced withwithTaskGrouppattern — auth work and 5-s timeout now race; winner cancels loser. No longer guarantees 5 s delay on every cold launch.
HistoryManager.swift
- D-12: iCloud KVS trim loop now properly re-encodes on each iteration;
?? cloudDatafallback removed to prevent infinite loop on encode failure; empty-array fallback added when cloudItems is fully drained.
HistoryView.swift
- A-10:
onChangerewired fromnfcWriter.isSuccesstonfcWriter.messageso consecutive rewrites (isSuccess staystrue) correctly append to history. - B-10/C-07:
navigationTitlecorrected fromsns.navigationTitletotab.history. - B-11: ResultBanner overlay gets
.padding(.bottom, 12)to clear system tab bar.
ToolFormView.swift
- C-10: Two separate
onAppearclosures merged into one, eliminating load-order ambiguity (review history + tool history + saved values now all in a single handler).
WriteEventUploader.swift
- C-08: Duplicate blocked-scheme guard in
migrateICloudHistoryIfNeededreduced to singleisBlocked()call.
BusinessGroupDetailView.swift
- B-13: "Company URL" tab in custom tab bar changed from
Button { onBack() }to a non-interactive active-state indicator (filled icon + semibold label).
2026-04-21 — Bug Hunt Round 1
NFCTool iOS
NFCWriter.swift
- A-01:
lockTagOnly()now resetsisSuccess = falsebefore starting session - D-06: Added
cancelSession()public method; called from ToolFormView on.backgroundscenePhase
ToolFormView.swift
- D-06: Added
@Environment(\.scenePhase)+.onChange(of: scenePhase)to cancel NFC session on background - StoreManager.swift: Removed redundant
trialStartKey; migration now removes UserDefaults entry after writing to Keychain (C-05)
ReviewTap iOS
ToolFormView.swift
- C-01: Write button now checks
nfcWriter.isShorteningin both disabled/label guard (parity with NFCTool) - D-06: Added scenePhase background handler to cancel NFC session
TagDetailView.swift
- D-04:
save()validates redirect URL (non-empty, http/https, non-empty host) before network call
NFCWriter.swift
- A-01:
lockTagOnly()now resetsisSuccess = false - D-06: Added
cancelSession()public method
StoreManager.swift
- C-05: Removed redundant
trialStartKey; migration removes UserDefaults entry after Keychain write
Localizable.xcstrings
- Added
reviewtap.tag.error.invalidRedirectUrl(en + ja)
Prior Sessions (summary)
| Date | Change |
|---|---|
| 2026-04-20 | nfc.bz: await logTap fix; in() filter encodeURIComponent fix |
| 2026-04-19 | iOS: migrate to nfc.bz API + subdomain URLs |
| 2026-04-18 | Widget: redesign UI, tap analytics, dashboard link |
| 2026-04-17 | Security: forceUnlockAll SCREENSHOT_MODE, consumable dedup, Keychain trial, 1MB iCloud trim |