@www.hyperlinks.space/program-kit 1.2.91881 → 18.18.18
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.
- package/.env.example +19 -0
- package/README.md +3 -3
- package/api/{base.ts → _base.ts} +1 -1
- package/api/wallet/_auth.ts +143 -0
- package/api/wallet/register.ts +151 -0
- package/api/wallet/status.ts +89 -0
- package/app/index.tsx +319 -5
- package/assets/images/PreviewImage.png +0 -0
- package/database/start.ts +0 -1
- package/database/wallets.ts +266 -0
- package/fullREADME.md +142 -71
- package/index.js +3 -0
- package/npmReadMe.md +3 -3
- package/package.json +7 -2
- package/scripts/test-api-base.ts +2 -2
- package/telegram/post.ts +44 -8
- package/docs/ai_and_search_bar_input.md +0 -94
- package/docs/ai_bot_messages.md +0 -124
- package/docs/blue_bar_tackling.md +0 -143
- package/docs/bot_async_streaming.md +0 -174
- package/docs/build_and_install.md +0 -129
- package/docs/database_messages.md +0 -34
- package/docs/fonts.md +0 -18
- package/docs/npm-release.md +0 -46
- package/docs/releases.md +0 -201
- package/docs/releases_github_actions.md +0 -188
- package/docs/scalability.md +0 -34
- package/docs/security_plan_raw.md +0 -244
- package/docs/security_raw.md +0 -345
- package/docs/timing_raw.md +0 -63
- package/docs/tma_logo_bar_jump_investigation.md +0 -69
- package/docs/update.md +0 -205
- package/docs/wallet_telegram_standalone_multichain_proposal.md +0 -192
- package/docs/wallets_hosting_architecture.md +0 -257
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
## Blue bar & cursor issue in GlobalBottomBar (Telegram Mini App, iOS)
|
|
2
|
-
|
|
3
|
-
### Context
|
|
4
|
-
|
|
5
|
-
- The issue appears **only inside the Telegram Mini App (TMA) on iOS**.
|
|
6
|
-
- The affected element is the **bottom AI input** implemented by `GlobalBottomBar` (`TextInput` wrapped in a `ScrollView`, with a custom scrollbar on the right).
|
|
7
|
-
- On **desktop web** and standard browsers, the input behaves as intended: normal caret, no intrusive system scrollbar; our custom scrollbar is visible and correct.
|
|
8
|
-
- On **Telegram Desktop TMA**, behavior also looks acceptable.
|
|
9
|
-
- On **Telegram iOS TMA**, a **thick vertical blue line** appears on the right side of the input, and it visually replaces/obscures the caret, especially near the right edge of the text.
|
|
10
|
-
|
|
11
|
-
### What the blue bar is
|
|
12
|
-
|
|
13
|
-
- The blue bar is **not our custom scrollbar** and not a React Native element.
|
|
14
|
-
- It is the **system scroll thumb** / scrollbar rendered by the iOS WebView that Telegram uses for Mini Apps:
|
|
15
|
-
- Appears when the underlying DOM element backing `TextInput` is scrollable.
|
|
16
|
-
- On iOS it is rendered as a thick blue bar overlay on the right.
|
|
17
|
-
- Its color and thickness are **controlled by the OS / WebView**, not standard CSS in many cases.
|
|
18
|
-
- Even when we:
|
|
19
|
-
- Add `scrollbar-width: none;` (Firefox),
|
|
20
|
-
- Add `-ms-overflow-style: none;` (IE/Edge),
|
|
21
|
-
- Add `::-webkit-scrollbar { width: 0; height: 0; }` (WebKit),
|
|
22
|
-
the iOS Telegram WebView **still draws** this overlay thumb in some configurations.
|
|
23
|
-
|
|
24
|
-
### Current RN Web / layout setup
|
|
25
|
-
|
|
26
|
-
- `TextInput` (RN Web) is rendered with:
|
|
27
|
-
- `multiline`
|
|
28
|
-
- `lineHeight = 20`
|
|
29
|
-
- Dynamic height controlled via `inputDynamicStyle` (1–8 lines).
|
|
30
|
-
- `scrollEnabled={Platform.OS !== "web"}` so **on web** we *try* to disable internal scrolling.
|
|
31
|
-
- Around this `TextInput` we have:
|
|
32
|
-
- An outer `ScrollView` that provides scrolling for long content.
|
|
33
|
-
- A **custom scrollbar** drawn along the bar edge, based on `scrollY` and `contentHeight`.
|
|
34
|
-
- We also:
|
|
35
|
-
- Inject a `<style>` tag on web to hide scrollbars on any `[data-ai-input="true"]`.
|
|
36
|
-
- Add a right-side overlay `View` (gutter) inside the input container on web, with:
|
|
37
|
-
- `position: "absolute"`, `right: 0`, `top: 0`, `bottom: 0`, `width: 10`,
|
|
38
|
-
- `backgroundColor: colors.background`,
|
|
39
|
-
- `pointerEvents: "none"`.
|
|
40
|
-
- Increase `paddingRight` on the input on web so the caret and last characters do not go under the overlay.
|
|
41
|
-
|
|
42
|
-
### Observed behavior by platform
|
|
43
|
-
|
|
44
|
-
- **Desktop web (normal browser)**:
|
|
45
|
-
- Native scroll thumb is hidden (CSS + overlay).
|
|
46
|
-
- Text caret is clearly visible at the end of the text.
|
|
47
|
-
- Our custom scrollbar works and reflects scroll position correctly.
|
|
48
|
-
|
|
49
|
-
- **Telegram Desktop TMA**:
|
|
50
|
-
- Behavior closely matches desktop web.
|
|
51
|
-
- No problematic blue bar; caret looks normal.
|
|
52
|
-
|
|
53
|
-
- **Telegram iOS TMA**:
|
|
54
|
-
- A thick **blue vertical bar** still appears on the right side of the input.
|
|
55
|
-
- The bar roughly aligns with where the platform scroll thumb would sit.
|
|
56
|
-
- It **does not fully respect** our CSS scrollbar-hiding rules.
|
|
57
|
-
- It appears even when:
|
|
58
|
-
- `scrollEnabled={false}` on the `TextInput` for web.
|
|
59
|
-
- We rely solely on the outer `ScrollView` for scroll.
|
|
60
|
-
- We overlay the right edge with a background-colored React `View`.
|
|
61
|
-
- The caret is technically present, but visually:
|
|
62
|
-
- The blue bar is more dominant than the caret and sits at the same x-position,
|
|
63
|
-
- So the user perceives it as a “weird blue cursor” instead of a scroll thumb.
|
|
64
|
-
- The last character near the right edge can appear slightly crowded/overlapped because of how the webview draws the thumb and because we are trying to line things up tightly near the arrow icon.
|
|
65
|
-
|
|
66
|
-
### Why Flutter/dart implementation did not show this issue
|
|
67
|
-
|
|
68
|
-
- The original Flutter implementation ran as **native UI** (or Flutter Web with different internals), not as React Native Web inside Telegram’s custom webview.
|
|
69
|
-
- Flutter’s text fields:
|
|
70
|
-
- Use their own composited rendering and native text controls.
|
|
71
|
-
- Have direct hooks into platform scroll behavior and caret rendering.
|
|
72
|
-
- Can fully own the scrollable area and scrollbar appearance.
|
|
73
|
-
- In contrast, RN Web:
|
|
74
|
-
- Uses the browser/Telegram WebView’s textarea or contenteditable under the hood.
|
|
75
|
-
- Has to live with OS/WebView scroll thumb behavior.
|
|
76
|
-
- Only partially controls scrollbars via CSS, which the iOS Telegram WebView may override.
|
|
77
|
-
|
|
78
|
-
### Why the bar is blue and how much we can style it
|
|
79
|
-
|
|
80
|
-
- On iOS Safari / WebView, the scroll thumb color and style are **not standard CSS-stylable**:
|
|
81
|
-
- There is no official `::-webkit-scrollbar-thumb` support for iOS Safari comparable to desktop.
|
|
82
|
-
- The color (blue) is tied to the system accent / theme and internal WebKit defaults.
|
|
83
|
-
- Telegram’s WebView may apply its own styling on top.
|
|
84
|
-
- Because of this:
|
|
85
|
-
- We **cannot reliably change** the thumb color to match our background.
|
|
86
|
-
- We **cannot reliably make it fully transparent** without also breaking scrolling in this embedded environment.
|
|
87
|
-
- Any success we get with `::-webkit-scrollbar*` rules on desktop Safari/Chrome does not necessarily carry over to Telegram iOS.
|
|
88
|
-
|
|
89
|
-
### Attempts made so far (and results)
|
|
90
|
-
|
|
91
|
-
1. **CSS-based hiding on `[data-ai-input="true"]`**
|
|
92
|
-
- Rules: `scrollbar-width: none;`, `-ms-overflow-style: none;`, `::-webkit-scrollbar { width: 0; }`.
|
|
93
|
-
- Works in normal browsers and some webviews.
|
|
94
|
-
- In Telegram iOS WebView, the blue bar persists.
|
|
95
|
-
|
|
96
|
-
2. **Disabling internal `TextInput` scrolling on web**
|
|
97
|
-
- `scrollEnabled={Platform.OS !== "web"}` to avoid the input’s own scrollable area.
|
|
98
|
-
- Rely on outer `ScrollView` + custom scrollbar.
|
|
99
|
-
- The blue thumb still appears in TMA iOS, suggesting the underlying DOM element is still treated as scrollable by the WebView (or the thumb is drawn by an outer layer).
|
|
100
|
-
|
|
101
|
-
3. **Right-edge overlay gutter**
|
|
102
|
-
- Relative container + absolutely positioned right-side `View` with `backgroundColor: colors.background`.
|
|
103
|
-
- `pointerEvents="none"` so it doesn’t break input.
|
|
104
|
-
- On desktop, this effectively hides any native thumb.
|
|
105
|
-
- On Telegram iOS, the blue bar can still appear visually on top or just beside that gutter, i.e. the overlay does not fully cover what the WebView paints.
|
|
106
|
-
|
|
107
|
-
4. **Extra `paddingRight` on the input**
|
|
108
|
-
- Prevents text/caret from touching the thumb/overlay region.
|
|
109
|
-
- Improves readability of the last character, but the blue line remains visible.
|
|
110
|
-
|
|
111
|
-
5. **DOM scroll listener + custom scrollbar**
|
|
112
|
-
- We now track `scrollTop` of the underlying DOM node and sync our custom scrollbar correctly.
|
|
113
|
-
- This is independent of the blue bar; it only ensures scroll indicator accuracy.
|
|
114
|
-
|
|
115
|
-
### Current understanding / constraints
|
|
116
|
-
|
|
117
|
-
- The blue bar on iOS Telegram Mini App is **owned by the host WebView**, not by our CSS or RN Web.
|
|
118
|
-
- That WebView:
|
|
119
|
-
- May treat the entire scrollable area (outer + inner) as a single scrollable region and draw an overlay thumb for accessibility/UX.
|
|
120
|
-
- May ignore some CSS attempts to hide or restyle the thumb.
|
|
121
|
-
- Because of these constraints:
|
|
122
|
-
- We **cannot guarantee complete removal** or recoloring of the blue bar in all TMA/iOS cases.
|
|
123
|
-
- The safest path is to ensure:
|
|
124
|
-
- The caret and text are not visually overlapped (padding + layout tweaks).
|
|
125
|
-
- Our own scrollbar remains accurate and subtle.
|
|
126
|
-
- We don’t break native input behavior (selection, IME, copy/paste, scroll).
|
|
127
|
-
|
|
128
|
-
### Potential future mitigations
|
|
129
|
-
|
|
130
|
-
- **Platform-specific layout tweaks for TMA iOS**:
|
|
131
|
-
- Detect `Telegram.WebApp.platform === "ios"` in JS and:
|
|
132
|
-
- Increase right gutter width.
|
|
133
|
-
- Slightly reduce the visible width of the input to give the thumb more room.
|
|
134
|
-
- Accept that the bar exists but keep it visually away from text/caret.
|
|
135
|
-
|
|
136
|
-
- **Experiment with non-scrollable inner element**:
|
|
137
|
-
- Render text inside a non-scrollable container and rely solely on outer scrolling.
|
|
138
|
-
- Risk: may break text selection behavior and IME interaction in TMA; needs careful testing.
|
|
139
|
-
|
|
140
|
-
- **Ask Telegram / WebView owners**:
|
|
141
|
-
- There may be feature flags or Meta tags specific to Telegram’s WebView that influence scrollbar presentation on iOS.
|
|
142
|
-
- If such an option exists, it would be the cleanest way to disable the blue overlay globally for the Mini App.
|
|
143
|
-
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
## Bot async streaming & Telegram constraints
|
|
2
|
-
|
|
3
|
-
This document captures the current behavior of the Telegram bot streaming path, the issues we observed, and the gap between the ideal "multi-segment async streaming" design and the current implementation.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Constraints and goals
|
|
8
|
-
|
|
9
|
-
### Telegram constraints
|
|
10
|
-
|
|
11
|
-
- **Per-message length limit:** Telegram's `parse_mode: "HTML"` messages must be ≤ **4096 characters** after HTML escaping and tagging.
|
|
12
|
-
- **HTML parsing rules:** Only specific tags are allowed (`<b>`, `<i>`, `<u>`, `<s>`, `<tg-spoiler>`, `<a>`, `<code>`, `<pre>`, `<blockquote>`). All `<`, `>`, `&`, `"` must be escaped unless they are part of a valid tag or entity; otherwise Telegram returns `Bad Request: can't parse entities`.
|
|
13
|
-
- **Delivery semantics:**
|
|
14
|
-
- A user's outgoing message shows a **clock** until Telegram's server accepts it, then switches to ✓/✓✓.
|
|
15
|
-
- Our bot's webhook `handleRequest` must return HTTP 2xx quickly; otherwise Telegram retries and may delay/hide the user's message.
|
|
16
|
-
- We return 200 immediately and process updates in `waitUntil`; updates for the **same chat** are serialized via a per-chat queue so Reply A is sent before we start processing Prompt B (see `app/bot/webhook.ts`).
|
|
17
|
-
|
|
18
|
-
### Product goals
|
|
19
|
-
|
|
20
|
-
- **Bot replies must be well-formed HTML and ≤4096 characters per message** so Telegram doesn't reject them.
|
|
21
|
-
- **Single message preferred;** when the AI output exceeds 4096 characters, the overflow is sent as **continuation message(s)** (each ≤4096), so the user sees the full reply.
|
|
22
|
-
- **Per-chat serialization at webhook:** updates for the same chat are processed one after another so Reply A is sent before we start Prompt B.
|
|
23
|
-
- **Cancellation within a chat:** we track a generation counter per chat; when a new message arrives for that chat, the in-flight stream is cancelled so only the latest reply is completed.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 2. Current implementation
|
|
28
|
-
|
|
29
|
-
### 2.1 AI layer (shared bot + TMA)
|
|
30
|
-
|
|
31
|
-
File: `app/ai/openai.ts`, `app/ai/transmitter.ts`
|
|
32
|
-
|
|
33
|
-
- `callOpenAiChat` / `callOpenAiChatStream` wrap the OpenAI Responses API (`client.responses.create` / `client.responses.stream`).
|
|
34
|
-
- They accept:
|
|
35
|
-
- `mode` (`"chat"` or `"token_info"`),
|
|
36
|
-
- `input` (text; for token_info a prefix is prepended in openai; for chat, history is prepended in transmitter),
|
|
37
|
-
- `context` (arbitrary metadata),
|
|
38
|
-
- optional `threadContext` (for DB persistence),
|
|
39
|
-
- optional `instructions` (string passed to the model; OpenAI native `instructions` field).
|
|
40
|
-
- **Bot** requests are initiated from `app/bot/responder.ts`, which sets `instructions: TELEGRAM_BOT_LENGTH_INSTRUCTION` on every transmit/transmitStream call. That instruction asks the model to keep replies under 4096 chars and to mention that full responses are available in TMA when the user asks for long messages.
|
|
41
|
-
- The AI layer does **not** derive instructions from `threadContext.type`; the caller (responder) supplies `instructions` when present. `transmitter` forwards `request.instructions` to the OpenAI layer.
|
|
42
|
-
- For **token_info**, openai still prepends a system-style prefix to `input` ("You are a blockchain and token analyst...").
|
|
43
|
-
- `transmit` / `transmitStream`:
|
|
44
|
-
- Claim the user message in the DB (`insertMessage` + `telegram_update_id`), or return `skipped` if another instance already handled it.
|
|
45
|
-
- For chat mode, prepend conversation history with `formatHistoryForInput`.
|
|
46
|
-
- Call OpenAI (with or without streaming).
|
|
47
|
-
- On success, persist the assistant `output_text` to the `messages` table.
|
|
48
|
-
|
|
49
|
-
### 2.2 Bot responder (Telegram side)
|
|
50
|
-
|
|
51
|
-
File: `app/bot/responder.ts`
|
|
52
|
-
|
|
53
|
-
#### 2.2.1 Concurrency & cancellation
|
|
54
|
-
|
|
55
|
-
- We track **per-chat generations** (key is `chatId`, not thread_id):
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
const chatGenerations = new Map<number, number>();
|
|
59
|
-
const numericChatId = typeof chatId === "number" ? chatId : undefined;
|
|
60
|
-
let generation = 0;
|
|
61
|
-
if (numericChatId !== undefined) {
|
|
62
|
-
const prev = chatGenerations.get(numericChatId) ?? 0;
|
|
63
|
-
generation = prev + 1;
|
|
64
|
-
chatGenerations.set(numericChatId, generation);
|
|
65
|
-
}
|
|
66
|
-
const isCancelled = (): boolean =>
|
|
67
|
-
numericChatId !== undefined &&
|
|
68
|
-
chatGenerations.get(numericChatId) !== generation;
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
- `shouldAbortSend()` checks the DB (`getMaxTelegramUpdateIdForThread`) for the latest user `telegram_update_id`; if the current update isn't the latest for that thread, it returns `true`.
|
|
72
|
-
- `transmitStream` receives:
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
{
|
|
76
|
-
isCancelled,
|
|
77
|
-
getAbortSignal: async () => (await shouldAbortSend()) || isCancelled(),
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
and uses this to abort the OpenAI stream if a newer prompt arrives.
|
|
82
|
-
|
|
83
|
-
- When a stream is cancelled mid-way, `responder.ts` calls `sendInterruptedReply`:
|
|
84
|
-
- If we already created and edited a message for this reply and have non-empty `streamedAccumulated`, we do one last `editMessageText` (HTML) to finish that message and persist the partial content.
|
|
85
|
-
- If the reply was cancelled before any text was sent but we have some accumulated content, we send a single capped HTML reply with that content and persist it.
|
|
86
|
-
- If we have no content and `sendToChat` is true, we may send a single "…" reply.
|
|
87
|
-
|
|
88
|
-
Together with the per-chat queue in the webhook (see 2.2.3), replies for the same chat are serialized so Reply A is sent before we start Prompt B; within that, the generation counter ensures the latest prompt wins and in-flight streams are cancelled.
|
|
89
|
-
|
|
90
|
-
#### 2.2.2 Streaming and overflow (multi-message)
|
|
91
|
-
|
|
92
|
-
- We use a **single streaming segment** for the first 4096 characters:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
const MAX_MESSAGE_TEXT_LENGTH = 4096;
|
|
96
|
-
let streamSentMessageId: number | null = null;
|
|
97
|
-
let streamedAccumulated = "";
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
- `sendOrEdit` is called on every `onDelta` from OpenAI and:
|
|
101
|
-
- Updates `streamedAccumulated`.
|
|
102
|
-
- Computes `slice = accumulated.slice(0, MAX_MESSAGE_TEXT_LENGTH)`.
|
|
103
|
-
- Formats `slice` via `mdToTelegramHtml` → `stripUnpairedMarkdownDelimiters` → `closeOpenTelegramHtml` → `truncateTelegramHtmlSafe`.
|
|
104
|
-
- Sends/edits a single Telegram message (first `sendMessage` with `"…"`, then `editMessageText` on `streamSentMessageId`), guarded by `sendOrEditQueue` and `editsDisabled`.
|
|
105
|
-
- After `transmitStream` completes:
|
|
106
|
-
- We flush the edit queue and perform a **final edit** on `streamSentMessageId` with the first 4096 characters from `result.output_text` (formatted as HTML, with plain fallback on error).
|
|
107
|
-
- If `result.output_text.length > MAX_MESSAGE_TEXT_LENGTH`, we call **`sendLongMessage`** with the remainder (`result.output_text.slice(MAX_MESSAGE_TEXT_LENGTH)`). Continuation messages are sent as separate Telegram messages (each chunk ≤4096), formatted with the same HTML pipeline and Markdown/plain fallback; they reply to the previous message so they appear in order.
|
|
108
|
-
- **Non-streaming path:** we call `transmit`; then if the result fits in one message we send a single reply; otherwise we call `sendLongMessage` with the full `result.output_text` (it chunks and sends multiple messages, each ≤4096).
|
|
109
|
-
- Helpers **`chunkText`** and **`sendLongMessage`** in responder split long text at newlines when possible and send multiple messages, replying to the previous one so the thread reads in order.
|
|
110
|
-
|
|
111
|
-
#### 2.2.3 Webhook concurrency
|
|
112
|
-
|
|
113
|
-
- In `app/bot/webhook.ts` we **serialize updates per chat** using a `chatQueue`:
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
const chatQueue = new Map<number, Promise<void>>();
|
|
117
|
-
// ...
|
|
118
|
-
const chatId = getChatIdFromUpdate(update);
|
|
119
|
-
const prev = chatId !== undefined ? chatQueue.get(chatId) : undefined;
|
|
120
|
-
const work = (prev ?? Promise.resolve())
|
|
121
|
-
.then(() => ensureBotInit())
|
|
122
|
-
.then(() => bot!.handleUpdate(update))
|
|
123
|
-
.then(() => { console.log('[webhook] handled update', updateId); })
|
|
124
|
-
.catch((err) => { console.error('[bot]', err); });
|
|
125
|
-
const tail = work.then(() => {}, () => {});
|
|
126
|
-
if (chatId !== undefined) chatQueue.set(chatId, tail);
|
|
127
|
-
waitUntil(work);
|
|
128
|
-
return jsonResponse({ ok: true });
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
- So for a given chat, the next update waits for the previous handler to finish. Reply A is sent before we start processing Prompt B, which avoids reorder flash. Different chats are still processed in parallel.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## 3. Gaps vs. ideal multi-segment streaming design
|
|
136
|
-
|
|
137
|
-
The ideal design would:
|
|
138
|
-
|
|
139
|
-
1. **AI always produces full `output_text`** (already true).
|
|
140
|
-
2. **Bot maintains multiple segment messages per reply:**
|
|
141
|
-
- Segment 0: chars `[0..4096)`, streamed live and finalized.
|
|
142
|
-
- Segment 1: chars `[4096..8192)`, streamed live in a second message, etc.
|
|
143
|
-
3. **After completion**, each segment is edited once more to match the exact final slice of `output_text`.
|
|
144
|
-
|
|
145
|
-
The **current implementation**:
|
|
146
|
-
|
|
147
|
-
- **First segment** is streamed live (single message, up to 4096 chars) and gets a final edit from `result.output_text`.
|
|
148
|
-
- **Overflow** (beyond 4096 chars) is sent **after** the stream completes via `sendLongMessage` as one or more continuation messages. We do **not** stream into the second (and further) segments live; only the first segment is streamed.
|
|
149
|
-
- So the user gets the full reply (first message + continuation messages), but only the first 4096 characters are streamed; the rest is sent in one go after completion. The TMA path still gets the full `output_text` (no truncation).
|
|
150
|
-
|
|
151
|
-
To implement **live streaming into multiple segments**, we would need to:
|
|
152
|
-
|
|
153
|
-
- In `sendOrEdit`, detect when the accumulated text crosses 4096 and create a new Telegram message for the next segment, then route subsequent edits to the correct segment.
|
|
154
|
-
- Maintain a `segments: { id: number; start: number; end: number }[]` (or similar) and issue a final `editMessageText` per segment on completion.
|
|
155
|
-
|
|
156
|
-
That refactor adds state and edge cases (segment creation, cancellation across segments). The current approach (stream first segment, then send overflow as continuation messages) is simpler and still delivers the full reply.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 4. Known issues / open questions
|
|
161
|
-
|
|
162
|
-
1. **"Clock + flash" in threads (historical vs. current):**
|
|
163
|
-
- With per-chat serialization at the webhook, Reply B does not start until Reply A's handler has finished (or at least until A's work is chained after). So we avoid overlapping replies in the same chat; the "clock + flash" reorder is largely avoided because A completes before B starts.
|
|
164
|
-
- Cancellation (generation counter) still matters when A is cancelled by B so that we don't keep editing A after B has been sent.
|
|
165
|
-
|
|
166
|
-
2. **Length vs. HTML edge cases:**
|
|
167
|
-
- We depend on the model respecting the 4096-character instruction; if it overshoots, we truncate, then trim at the last `>` and call `closeOpenTelegramHtml` to avoid cutting tags in half.
|
|
168
|
-
- "Bad Request: can't parse entities" can still occur on malformed HTML; `stripUnpairedMarkdownDelimiters` + `closeOpenTelegramHtml` + `truncateTelegramHtmlSafe` reduce this.
|
|
169
|
-
|
|
170
|
-
3. **Segment-level streaming:** Only the first segment is streamed live; overflow is sent after completion. For truly long live multi-segment streaming we'd need the refactor described in §3.
|
|
171
|
-
|
|
172
|
-
4. **Behavior when cancellation happens late:** If `isCancelled()` flips very close to the end of a stream, the final edit might still land. We check `shouldAbortSend()` in `sendOrEditOnce` and before the final completion edit to reduce this.
|
|
173
|
-
|
|
174
|
-
If you want to adjust any of these behaviors (e.g. live multi-segment streaming, or how we finalize a cancelled reply), we can extend this design and update `responder.ts` accordingly.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# Build and Install: Faster & Better
|
|
2
|
-
|
|
3
|
-
This doc describes the Windows Electron build and install flow and how to speed them up and improve them.
|
|
4
|
-
|
|
5
|
-
Exe creating example:
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
cd /d C:\1\1\1\1\1\HyperlinksSpaceProgram
|
|
9
|
-
npm run build:win:verbose
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Exe bash run example:
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
powershell -NoProfile -Command "Start-Process -FilePath 'C:/1/1/1/1/1/HyperlinksSpaceBot/releases/builder/build_03252026_1449/HyperlinksSpaceProgramInstaller_03252026_1449.exe'"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 1. Build process
|
|
21
|
-
|
|
22
|
-
### What runs
|
|
23
|
-
|
|
24
|
-
`npm run build:win` does three things:
|
|
25
|
-
|
|
26
|
-
1. **Expo web export** – `npm run build` → `expo export -p web`. Metro bundles the app and writes static files to `dist/`. Usually the slowest step (tens of seconds).
|
|
27
|
-
2. **Electron pack** – `electron-builder --win`. Rebuilds native deps (if any), packages the app, builds the NSIS installer. Downloads (Electron, NSIS, winCodeSign) are cached after first run.
|
|
28
|
-
3. **Clean** – `node windows/cleanup.cjs`. Moves artifacts into `releases/builder/build_MMDDYYYY_HHMM/` (installer only at root; zip, yml, unpacked, and other artifacts under `dev/`).
|
|
29
|
-
|
|
30
|
-
**Output:** `releases/builder/build_<date>_<time>/HyperlinksSpaceProgramInstaller_<stamp>.exe` at root and `dev/` (portable zip, latest.yml, zip-latest.yml, win-unpacked, blockmap, builder-debug.yml, builder-effective-config.yaml).
|
|
31
|
-
|
|
32
|
-
### How to make the build faster
|
|
33
|
-
|
|
34
|
-
| Goal | What to do |
|
|
35
|
-
|------|------------|
|
|
36
|
-
| **Skip Expo when only Electron changed** | Use `npm run pack:win` when `dist/` is already up to date (no Expo app changes). Saves the full Expo/Metro run (often 30–60+ s). |
|
|
37
|
-
| **Skip native rebuild** | In `package.json` → `build`, add `"npmRebuild": false`. The packaged app only runs the web bundle; native rebuild can often be skipped. Try it; if the app runs, keep it. Saves time every build. |
|
|
38
|
-
| **Avoid building the installer when iterating** | Use `npm run build:win:dir` when you only need the unpacked app (e.g. quick runs from `release/win-unpacked/app.exe`). Skips NSIS and clean; faster and no “file locked” risk. |
|
|
39
|
-
| **Keep caches** | Don’t clear Metro/Expo cache unless needed. Electron and electron-builder caches (e.g. under `%LOCALAPPDATA%\electron-builder\Cache`) are reused; leave them. |
|
|
40
|
-
| **Verbose only when debugging** | Use `npm run build:win:verbose` only to diagnose failures; normal builds are quicker without DEBUG. |
|
|
41
|
-
| **Faster installer step (7z/NSIS)** | The slowest step is 7-Zip compressing the packed app (~31k files, hundreds of MB). Use `build:win:dir` when you don’t need the installer. For full builds, set `nsis.compression: "normal"` or `"store"` in `package.json` → `build.nsis` to trade installer size for speed (e.g. 50–70% faster with `"store"`). |
|
|
42
|
-
|
|
43
|
-
### Rebuilding only what changed
|
|
44
|
-
|
|
45
|
-
- **Caches:** Expo/Metro and electron-builder already use caches. A full `build:win` reuses cached transforms and packed files where nothing changed, so rebuilds are partly incremental by default.
|
|
46
|
-
- **Skip the web build when only Electron changed:** If you only changed `windows/build.cjs` or other files in `windows/`, the icon, or `package.json` build config (not the React app code), run **`npm run pack:win`** instead of `build:win`. That runs only electron-builder + clean and reuses the existing `dist/`. Ensure `dist/` is up to date (run `npm run build` once, or after any Expo app changes).
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 2. Install process
|
|
51
|
-
|
|
52
|
-
### What runs
|
|
53
|
-
|
|
54
|
-
User runs **`HyperlinksSpaceProgramInstaller.exe`**. NSIS extracts the app (e.g. to `%LOCALAPPDATA%` or Program Files), creates shortcuts, and optionally adds Start Menu / Desktop entries. After that, launching the app runs the already-extracted exe (no extraction at startup → fast launch).
|
|
55
|
-
|
|
56
|
-
### How to make install faster and more reliable
|
|
57
|
-
|
|
58
|
-
| Goal | What to do |
|
|
59
|
-
|------|------------|
|
|
60
|
-
| **Avoid “file locked” / long waits** | Add the project or `release` folder to Windows Defender (or AV) exclusions so the new exe isn’t scanned/locked during build. For end users, a one-time exclusion for the installer download folder can reduce install time. |
|
|
61
|
-
| **Faster first launch after install** | Install to an SSD if possible. First launch may trigger AV scan once; exclusions help. |
|
|
62
|
-
| **Fewer prompts** | NSIS can be configured for one-click install (no “Choose directory” step) to shorten the flow; current config can be tuned in `build.nsis` (e.g. `oneClick: true` if desired). |
|
|
63
|
-
| **Run installer as admin only if needed** | For per-machine install to Program Files, run the installer as Administrator. For per-user install (default), no admin needed. |
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## 3. Improvements for both
|
|
68
|
-
|
|
69
|
-
| Area | Suggestion |
|
|
70
|
-
|------|------------|
|
|
71
|
-
| **Code signing** | Sign the installer and app exe (e.g. Windows Authenticode) so Windows and AV trust it. Reduces warnings and can speed up install/launch. |
|
|
72
|
-
| **Auto-updates** | Add `electron-updater` (or similar) and serve updates over HTTPS so users get patches without reinstalling. |
|
|
73
|
-
| **CI/CD** | In CI, cache `node_modules`, `.expo`, Metro cache, and `electron-builder` cache to make repeated builds much faster. |
|
|
74
|
-
| **Developer Mode (Windows)** | If you build on Windows and use exe editing (icon, etc.), Developer Mode avoids symlink errors during the winCodeSign step. |
|
|
75
|
-
| **Structured releases** | `windows/cleanup.cjs` puts each build in `releases/builder/build_<date>_<time>/` with the installer at root and all other artifacts in `dev/`. |
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## 4. Quick reference
|
|
80
|
-
|
|
81
|
-
| Script | Use when |
|
|
82
|
-
|--------|----------|
|
|
83
|
-
| `npm run build:win` | Full build: Expo export + Electron + NSIS + clean. Use for releases. |
|
|
84
|
-
| `npm run pack:win` | Electron + clean only; reuses existing `dist/`. Use when only Electron/wrapper changed (no app code). |
|
|
85
|
-
| `npm run build:win:dir` | Same export + pack but no installer; output is `release/win-unpacked/`. Use for quick local runs. |
|
|
86
|
-
| `npm run build:win:verbose` | Full build with `DEBUG=electron-builder` for troubleshooting. |
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## 5. File layout after build
|
|
91
|
-
|
|
92
|
-
- **After `build:win` or `pack:win`:** `releases/builder/build_MMDDYYYY_HHMM/HyperlinksSpaceProgramInstaller_<stamp>.exe` and `releases/builder/build_MMDDYYYY_HHMM/dev/` (portable zip, yml, win-unpacked, blockmap, builder-debug.yml, builder-effective-config.yaml).
|
|
93
|
-
- **After `build:win:dir`:** `release/win-unpacked/` (run `app.exe` from there).
|
|
94
|
-
- **Build inputs:** `dist/` (Expo export), `windows/` (build.cjs, app-shell.html, preload-log.cjs), `assets/icon.ico`, and files listed under `build.files` in `package.json`.
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 6. Conclusions (build verbosity and improvements)
|
|
99
|
-
|
|
100
|
-
### What the slow step is
|
|
101
|
-
|
|
102
|
-
When you run a full Windows build with verbose logging (`build:win:verbose`), the long part is **7-Zip compressing the packed app** for the NSIS installer. electron-builder:
|
|
103
|
-
|
|
104
|
-
1. Builds the unpacked app (Expo export + Electron pack) into `release/win-unpacked/`.
|
|
105
|
-
2. Runs 7za to compress that into a single archive (e.g. `expo-template-default-53.0.43-x64.nsis.7z`).
|
|
106
|
-
3. Runs NSIS to wrap that archive into `HyperlinksSpaceProgramInstaller.exe`.
|
|
107
|
-
|
|
108
|
-
Step 2 compresses a large number of files (tens of thousands) and hundreds of MB, so it dominates build time. The final installer size is much smaller (e.g. ~141 MB) but the compression work is heavy.
|
|
109
|
-
|
|
110
|
-
### Why the payload is big
|
|
111
|
-
|
|
112
|
-
The packed app under `release/win-unpacked/resources/app/` should contain only what’s in `build.files` (e.g. `dist/**/*`, `windows/**`, `assets/icon.ico`). If you see `node_modules` or other unneeded folders there, the payload is larger than necessary. Check with:
|
|
113
|
-
|
|
114
|
-
- `dir release\win-unpacked\resources\app` (or list that folder) before the installer step.
|
|
115
|
-
|
|
116
|
-
If `node_modules` is present, tighten `build.files` or add exclusions so only what the packaged app needs is included; that will also speed up the 7z step.
|
|
117
|
-
|
|
118
|
-
### How to improve the process
|
|
119
|
-
|
|
120
|
-
| Improvement | Action |
|
|
121
|
-
|-------------|--------|
|
|
122
|
-
| **Skip the slow step when possible** | Use `npm run build:win:dir` for daily work. You get the unpacked app and can run it from `release/win-unpacked/app.exe`; 7z and NSIS are skipped. |
|
|
123
|
-
| **Faster 7z at the cost of installer size** | In `package.json` → `build.nsis`, set `"compression": "normal"` or `"store"`. `"store"` (no compression) can cut installer build time by roughly 50–70%; the resulting `.exe` will be larger. |
|
|
124
|
-
| **Keep the table in §1** | Use the “How to make the build faster” table (skip Expo when possible, `npmRebuild: false`, AV exclusions, etc.) together with the compression and dir-build options above. |
|
|
125
|
-
|
|
126
|
-
### Summary
|
|
127
|
-
|
|
128
|
-
- **Slow step:** 7-Zip compressing the packed app (many files, large size) before NSIS.
|
|
129
|
-
- **Improvements:** Use `build:win:dir` when you don’t need the installer; lower NSIS compression for faster full builds; ensure only needed files are packed (no stray `node_modules`); use the other tips in this doc (pack-only script, caches, AV exclusions).
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
### Database refactor and extensions (plan)
|
|
2
|
-
|
|
3
|
-
**Goal**
|
|
4
|
-
|
|
5
|
-
- DB bootstrap in `app/database`; keep `users`, `wallets`, `pending_transactions` as-is.
|
|
6
|
-
- One minimal AI table shared by bot and TMA; one user identity: `user_telegram` (bot: `ctx.from.username`; TMA: initData `user.username`). No Chat table; TMA-only users use `type = 'app'`.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
**messages** (single table, not implemented yet)
|
|
11
|
-
|
|
12
|
-
- `id` (PK, BIGSERIAL) — same as other tables (e.g. wallets).
|
|
13
|
-
- `created_at` (TIMESTAMPTZ, NOT NULL DEFAULT now()).
|
|
14
|
-
- `user_telegram` (TEXT, FK → `users(telegram_username)`).
|
|
15
|
-
- `thread_id` (BIGINT) — bot: Telegram `message_thread_id` (0 = default); TMA: app-chosen (e.g. 0).
|
|
16
|
-
- `type` (TEXT) — `'bot'` | `'app'`.
|
|
17
|
-
- `role` (TEXT) — `'user'` | `'assistant'` | `'system'`.
|
|
18
|
-
- `content` (TEXT).
|
|
19
|
-
- `telegram_update_id` (BIGINT, nullable) — bot only; NULL for TMA. Source: Telegram webhook payload (`ctx.update.update_id` in bot handler). Used for: (1) unique constraint → one row per update per thread, no double-reply; (2) before send, check max = our update_id → if not, abort (avoids mixed replies in serverless). TMA doesn’t need it: requests come from the app (HTTP), not Telegram’s update stream, so there is no update_id; TMA concurrency is a separate concern (e.g. client or request-scoped).
|
|
20
|
-
|
|
21
|
-
**Thread key** — `(user_telegram, thread_id, type)` together identify one conversation. Example: user "alice", topic 0, bot = one thread; same user, topic 5, bot = another thread; same user, app = TMA thread.
|
|
22
|
-
|
|
23
|
-
**Index** — `(user_telegram, thread_id, type, created_at)` so we can quickly get "all messages in this thread, ordered by time" (for building chat history for AI).
|
|
24
|
-
|
|
25
|
-
**Unique for bot** — `(user_telegram, thread_id, type, telegram_update_id)` where `telegram_update_id IS NOT NULL`. Meaning: in a given thread, each Telegram update_id may appear at most once. So we can’t insert two rows for the same update (e.g. duplicate webhook or two serverless instances); the second insert fails, that handler skips. Only the first insert “owns” the reply.
|
|
26
|
-
|
|
27
|
-
**Bot: no message mixing (messages-only)**
|
|
28
|
-
|
|
29
|
-
1. Insert user message with `telegram_update_id`. On unique violation → skip.
|
|
30
|
-
2. Before each send: if `MAX(telegram_update_id)` for thread ≠ our update_id → abort.
|
|
31
|
-
3. No extra tables.
|
|
32
|
-
|
|
33
|
-
**Migrations**
|
|
34
|
-
|
package/docs/fonts.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Fonts (Expo web / TMA)
|
|
2
|
-
|
|
3
|
-
## What the bottom bar asks for today
|
|
4
|
-
|
|
5
|
-
- **Name in code:** **`Aeroport`** (geometric / grotesk-style sans — the brand face used elsewhere in the product).
|
|
6
|
-
- **Reality in this repo:** There is **no** Aeroport font file under `app/assets/` and **no** `@font-face` rule, so **the browser cannot load “Aeroport”**. It falls back — in Telegram’s WebView that often looks like a **serif** (“antiqua”), not a grotesk.
|
|
7
|
-
|
|
8
|
-
## What you see after the fix
|
|
9
|
-
|
|
10
|
-
- **`app/fonts.ts`** exports **`WEB_UI_SANS_STACK`**: **`Aeroport` first** (for when files exist), then **`ui-sans-serif, system-ui, …, sans-serif`** so text always uses a **modern sans** until Aeroport is bundled.
|
|
11
|
-
|
|
12
|
-
## How to use real Aeroport (optional)
|
|
13
|
-
|
|
14
|
-
1. Add licensed `.otf`/`.ttf` files, e.g. `app/assets/fonts/Aeroport-Regular.otf`.
|
|
15
|
-
2. Register with **`expo-font`** in the root layout, e.g. `useFonts({ Aeroport: require("./assets/fonts/Aeroport-Regular.otf") })`.
|
|
16
|
-
3. Add **`@font-face`** in `global.css` if you prefer pure CSS loading on web (paths must match the exported static asset URL).
|
|
17
|
-
|
|
18
|
-
Until then, the **stack** in `global.css` + `GlobalBottomBarWeb` keeps the UI **grotesk-like** via system fonts.
|
package/docs/npm-release.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Milestone snapshot package (npm)
|
|
2
|
-
|
|
3
|
-
This repository includes a publishable snapshot package for fast developer bootstrap:
|
|
4
|
-
|
|
5
|
-
- package source: repository root (published directly)
|
|
6
|
-
- **npmjs (public):** `@www.hyperlinks.space/program-kit` — manage org and tokens: [www.hyperlinks.space on npm](https://www.npmjs.com/settings/www.hyperlinks.space/packages)
|
|
7
|
-
- **GitHub Packages:** `@hyperlinksspace/program-kit` (same version; GitHub requires the package scope to match this repo's owner)
|
|
8
|
-
|
|
9
|
-
## Verify publish payload locally
|
|
10
|
-
|
|
11
|
-
The npm package page uses `README.md` from the published tarball, not `npmReadMe.md`. The published package also includes **`fullREADME.md`**, a copy of the developer readme (saved before the npm readme replaces `README.md`). Match CI before `npm pack`, then restore:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
cp README.md fullREADME.md
|
|
15
|
-
cp npmReadMe.md README.md
|
|
16
|
-
npm pack --dry-run
|
|
17
|
-
git checkout -- README.md
|
|
18
|
-
rm -f fullREADME.md
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Install snapshot as a developer
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx @www.hyperlinks.space/program-kit ./my-hsp-app
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The CLI materializes the bundled package payload into your target folder, then you run:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
cd my-hsp-app
|
|
31
|
-
npm install
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Release channels
|
|
35
|
-
|
|
36
|
-
- `latest`: immutable stable snapshots (tag workflow `snapshot-vX.Y.Z`)
|
|
37
|
-
- `next`: rolling snapshots from manual workflow dispatch
|
|
38
|
-
|
|
39
|
-
In the output, you'll find options to open the app in:
|
|
40
|
-
|
|
41
|
-
- [a development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
42
|
-
- [an Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
43
|
-
- [an iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
44
|
-
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
45
|
-
|
|
46
|
-
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|