Haru Space Lab
A green connection status was not proof that the user flow was complete
A record of connecting external market signals, mobile loading, and project traffic—and learning to verify one path from browser through API, ledger, provider, and final screen.

Summary
At a glance
- External market signals were routed only to a chat room selected by a group administrator, with allowed-field parsing, deduplication, and separation from AI context.
- We fixed mobile loading against the entire rendered screen, including the dynamic viewport and safe area, rather than against the component alone.
- We found that connection verification and the real dashboard used different contracts, then made verification exercise the same full-report and cache path.
This retrospective was reconstructed from Haru Space design documents, Git history, and operational issues dated August 24–26, 2026. It does not disclose actual webhook addresses or connection keys, chat-room or project identifiers, credentials, internal APIs, or original provider responses.
Development screens contain plenty of green statuses: connected, active, request succeeded, ready, response 200.
Those indicators are necessary. But each one describes success in a particular layer; it does not mean that the user got what they wanted all the way through.
An external webhook can receive a 200 while no message appears in the chat room. A loading card can fill the nominal screen height while a black strip remains below the mobile safe area. An analytics connection check can succeed while the real traffic dashboard continues to say that verification is required.
Three different issues converged on the same lesson.
“Connected” describes the state of a configuration. To claim completion, we had to verify one continuous path from the browser through the API, storage ledger, and external provider to the last screen the user sees.
Which room should receive an external market signal?
The investment area already had a way to receive webhook signals from an external charting service. At first, those signals were stored only in an investment-alert list. What the user wanted was to view them together with group members in a particular chat room.
“Send it to a chat room” was not a simple copy operation. We had to reconsider who had the authority to choose the destination of an external request.
If a payload could supply a group code and room identifier, an external caller who knew the connection secret could redirect the message. Copying the complete original JSON into chat could bring credentials or arbitrary prose with it. Saving it as an ordinary bot message could also mix external input into the AI’s recent conversation context, where it might behave like an instruction.
We removed the destination from the external payload.
- A group administrator selects an ordinary chat room inside Haru Space where the administrator participates and has permission to write.
- The server stores that selection together with a public reference for the connection.
- The external request can prove that it knows the secret for that connection, but it cannot choose a group or room.
- Only allowed fields such as signal type, symbol, time, price, and volume are parsed.
- Arbitrary messages and unknown fields are neither stored nor copied into chat.
- The result becomes a structured notification event in all three languages and is excluded from AI conversation context.
To prevent a retried request from creating the same message several times, we built a delivery ledger around a fingerprint of the normalized signal. The ledger and chat message were committed in the same transaction. Realtime updates reused the existing path, while push notification remained a secondary best-effort action. A push failure did not rerun the main webhook operation and duplicate the chat message.
The webhook’s success criterion was not its HTTP response. Success meant that the correct connection left exactly one safe event in the correct room—and that the event could not be reused as an AI instruction.
A copyable template was also part of input validation
The first signal template contained a syntax error, and the event field was not being used for classification. Different signals could therefore appear as the same generic notification.
Fixing only the server parser would not improve the experience of pasting a template into TradingView. We made complete JSON templates for the two signals copyable from the app, then had the server recheck that the event and state flags agreed. A non-numeric value or unsupported event was rejected rather than silently repaired.
We tested the template supplied by the UI against the same server contract. An example copied by the user needed to work as real input, while an invalid hand-edited request needed to fail safely.
The loading screen was not the whole screen
That same week, a black area appeared below the light loading screen in mobile Safari. The loading component itself had a light background and was configured to occupy the full screen. The problem was hard to see on a PC or in an ordinary mobile preview.
The cause was outside the component.
The document root had a dark default background. A mobile browser’s dynamic viewport and bottom safe area could briefly expose that document background outside the fixed overlay. The viewport configuration was also insufficient for using the device’s top and bottom regions as intended.
The fix was more than adding padding to the loading card.
- While loading, we synchronized the document root and screen to the same themed background.
- We supported fixed, small, and dynamic viewport heights in fallback order.
- We included the safe-area insets on all four sides in the loading screen’s real area.
- We prevented overscroll from exposing a different root background.
- When loading ended, we removed the temporary root state.
We also changed the verification target from the component’s bounding box to every visible part of the actual mobile screen. Across light and dark themes and different mobile heights, we checked that the loading surface, document root, and body backgrounds matched.
An error visible to the user can originate outside a component boundary. End-to-end verification extended beyond the DOM hierarchy to the viewport drawn by the browser.
We chose not to collect group traffic ourselves
Because Haru Space manages group projects, group members also wanted to see whether people were actually visiting their project. We could have introduced a separate proxy or raw-log collector, but that would have added too many operational points and costs for the first stage.
We chose to use the aggregate Web Analytics API already provided by the platform hosting the projects.
Connection settings stayed in the existing Vercel card under group administration, while the actual metrics appeared in a separate Project traffic group menu. This separated the people who configure the connection from those who use the report day to day.
The dashboard provided only aggregate human-visitor data.
- Pageviews and visitors by period
- Daily trends
- Top routes and referring hostnames
- Country and device-type categories
Haru Space did not store original IP addresses, User-Agent strings, query strings, individual request paths, or raw visit events. Bots and AI crawlers were not mixed into the human-visitor metrics; users were directed to a separate provider view for that traffic.
The dashboard was not presented as a billing screen or a replacement for security logs. A monthly pageview threshold was advisory for the group, not a claim about the provider’s billing ceiling.
We did not show one external project’s totals as two groups’ numbers
At the time, the provider’s public aggregate API did not offer a contract that could separate multiple hostnames inside one project in the way we needed for group isolation. If one external project served domains for several groups, we could not label the combined total as the traffic of each group.
Instead of guessing from domain strings for convenience, we allowed one external project to be connected to only one group. Duplicate connections were blocked across the platform, and any ownership transfer would require a separately approved process that preserved the previous group’s ledger and historical aggregates.
Even a platform administrator did not automatically gain access to a private group’s traffic. We separated the authority to manage infrastructure connections from the authority to read group-level metrics, and rechecked active membership in the current group.
Numbers can look less sensitive than private conversation, but they reveal a group’s activity and growth. Aggregate data still needed to remain inside the group’s sovereignty boundary.
An external analytics failure did not become a group-work failure
Calling the provider again every time someone opened the traffic screen would be slow and wasteful, and it would pass every external outage straight into the user experience.
We kept a short cache for each reporting period and used a database lease so that multiple server instances would not refresh it at the same time. When a provider request failed, the last good aggregate remained available and was marked stale. Only when no successful data had ever existed did the UI show an empty state tailored to the current condition.
Errors were separated into authentication, insufficient permission, analytics-resource checks, request limiting, and temporary failure. We stored only a constrained recovery code, never the provider’s response body or the request URL.
Chat, project work, and deployment still needed to function while external analytics was unavailable. An observation feature’s failure could not be mistaken for failure of the work itself.
The connection check succeeded, but the dashboard failed
The most important operational error appeared after the user had completed the setup.
Verify connection succeeded in the development-connection screen. The API also returned normally, and the stored connection and minimally scoped analytics credential were active. Yet the same group’s project traffic page continued to say that verification was required.
The connection check and the real dashboard were validating different contracts.
The connection check fetched only one overall visit count for a short period. The dashboard parsed that count together with daily, route, referrer, country, and device aggregates. The external provider could legitimately return a row without a category name for cases such as direct or unclassified traffic. The Haru Space parser treated that single row as grounds to discard the entire report as invalid.
We fixed both sides together.
- A row without a category name is excluded only from that category table; the overall summary and other valid rows remain.
- Connection verification succeeds only after it fetches the same complete report as the dashboard and prepares its cache.
This was not a change to accept every value permissively. Daily timestamps, pageviews, and visitor counts remained under a strict safe-integer contract. Invalid numbers were not dressed up as zeros. A failure log recorded only which aggregate dimension did not match, leaving out credentials, filters, and response values.
We had used the green light from a small probe as the green light for the whole feature.
“Connected to Vercel” did not mean one credential for every purpose
Initially, we tried to reuse the Vercel OAuth credential for deployment and domain connections when calling the analytics API. Even with the correct project and visit data visible in the provider dashboard, the analytics request did not produce the expected result.
The investigation showed that the official permission scopes for the two capabilities were different. We could not assume that a deployment credential also included analytics access.
Analytics therefore received a separate, minimally scoped credential limited to the current team and one project. We checked actual analytics capability before storing it and kept it encrypted on the server. The client received only whether it was configured and when it had been updated; not even a partial value was displayed again. A credential was not moved automatically when the connected project changed.
“The same provider” did not mean “the same authority.” Treating deployment, domains, and analytics as a single connection switch would have broken least privilege.
An end-to-end verification checklist
These three cases left us with a shared sequence of checks.
1. What exactly did the user submit?
Verify the copyable template, selected chat room, mobile theme, and analytics project using the actual UI values.
2. Does the server decide the destination and authority again?
Do not trust arbitrary identifiers from an external payload or URL. Revalidate the current group, room, and project ownership on the server.
3. How do the ledger and cache represent duplicates and failures?
Store a concurrent retry once, never overwrite a failure as success, and distinguish the last good aggregate during an external outage.
4. Did we exercise the external provider’s real contract all the way through?
Do not stop at a connection probe. Verify the same authentication and parsing path used by the full report that the user will see.
5. Did we inspect the last screen in a real environment?
In addition to component tests, inspect the mobile viewport, the realtime message in the selected chat, and the dashboard’s empty, error, and stale states.
6. Can a secondary feature fail without rerunning the primary operation?
A push, analytics, or progress-event failure must not duplicate a webhook message or project job.
We do not call remaining verification complete
During this period, the external-signal delivery, mobile-loading fix, and traffic-dashboard code progressed through verification and merge. We also found the cause of the mismatch between traffic connection verification and the complete report, then corrected the contract.
The repository record, however, still listed some final operational use and physical-device checks as follow-up observations. This article therefore does not say that every flow was fully verified. Passing automated tests, builds, and operational API checks is different from watching the entire path in the room, device, and project selected by a real user.
That distinction was itself the week’s most important result.
Operational verification began not when we added more green statuses, but when we could say what each green light proved—and what it did not prove yet.
Leaving a reaction may store a random identifier in this browser to prevent duplicates.
