diff --git a/WIP.md b/WIP.md index 0df1030c..170dacf8 100644 --- a/WIP.md +++ b/WIP.md @@ -428,6 +428,34 @@ WIP.md itself (and other files outside `docs/`) is not rendered through tbdocs a Python scripts are reserved for non-render concerns: one-off content conversion (e.g. `scripts/convert_em_dash_separators.py`), repo audits, dev tooling, link checks beyond `check.bat`. They are never a prerequisite for the render pipeline. +`wisdom/` — Discord knowledge-harvesting tool (three-phase: export → process → extract). Plans in `wisdom/PLAN-{1,2,3}.md`; implementation under `wisdom/`. Uses only Node.js built-in APIs. + +### Wisdom Phase 3 --- Extract invocation + +Phase 3 runs Claude agents over the processed thread `.md` files and drafts documentation additions. The default flow is **incremental**: only threads whose Discord-side content has actually changed since the last successful merge are re-extracted. Combined with the incremental `export` and `process` phases, the routine update is three commands with no flags: + +``` +node wisdom/wisdom.mjs export +node wisdom/wisdom.mjs process +node wisdom/wisdom.mjs extract +``` + +The `extract` step itself is a three-stage flow that Claude orchestrates: prep → workflow → merge. The merge step grafts new findings into the long-lived `staging.md`, replacing matching sections in place and emitting `[REFINED?]` markers for findings whose prior version has been reviewed and removed --- so pending review work is never clobbered. + +1. **Prep**: `node wisdom/wisdom.mjs extract` filters threads against `wisdom/data/findings/extract-state.json` (the per-thread `last_message_id` + `message_count` watermark, advanced on each successful merge). Only changed threads survive the filter. Shared reference files (`package-summary.txt`, `page-index.json`) are written once; per-batch files contain thread file paths, per-thread file sizes, config, and a `mode` field (`incremental`, `since`, `all`, or `force`) consumed by the merge step. + - If the filter is empty (no threads have changed), prep exits with `No new threads since the last successful merge` and the merge step is unnecessary. +2. **Run the workflow**: check which prep layout was produced: + - **Single-batch** (≤200 threads) --- `extract-prep.json` exists: read it, pass its parsed contents as `args` to the Workflow tool with `scriptPath: "wisdom/extract/workflow.mjs"`. Write the returned `{ additions: [...] }` array to `wisdom/data/findings/extract-results-0.json`. + - **Multi-batch** (>200 threads) --- `extract-manifest.json` exists: read it, then for each entry in `manifest.batches`, read the corresponding `extract-batch-{i}.json`, pass it as `args` to the Workflow tool, and write the returned additions array to `extract-results-{i}.json`. Skip batches whose result file already exists (resumability). +3. **Merge**: `node wisdom/wisdom.mjs extract --merge` reads every `extract-results-*.json`, grafts the additions into `staging.md` per the merge semantics in [wisdom/PLAN-3.md](wisdom/PLAN-3.md#merge-semantics), and advances `extract-state.json`. Atomic write via temp+rename; previous `staging.md` retained as `staging.md.bak` for one generation. +4. **Review**: `wisdom/data/findings/staging.md` is the long-lived human-review file --- grouped by target page, with `[DUPLICATE?]` markers for cross-thread overlaps, `[REFINED?]` markers for previously-processed findings whose source thread has grown, and an `Unmapped Findings` section for findings with no existing doc page. The reviewer can append `[LOCKED]` to any section header to prevent auto-replacement on the next merge. + +**Diagnostic modes** (mutually exclusive primary flags): +- `extract --since 2026-06-04` --- filter by thread `created` date instead of watermark. State is not touched; merge writes to `staging-since-2026-06-04.md` rather than grafting into the canonical staging file. +- `extract --all` --- bootstrap / re-baseline. Processes every thread regardless of state and ignores the channel filter. Merge still grafts (replacing matching sections, inserting new ones) so an existing `staging.md` is not lost. +- `extract --force` --- ignores the watermark filter but respects `--channel`. Useful when the agent prompt changes and specific channels need re-extraction. +- `extract --dry-run` --- writes the prep file but does not invoke the workflow; state is not touched. + ## Build pipeline The site builds via [builder/](builder/), a custom Node.js static site generator (`tbdocs`). See [builder/PLAN.md](builder/PLAN.md) for the architecture overview, [builder/README.md](builder/README.md) for the quickstart, and the [tbdocs Internals](docs/Documentation/Builder.md) site page for the high-level tour. diff --git a/docs/Documentation/Wisdom.md b/docs/Documentation/Wisdom.md new file mode 100644 index 00000000..088a3caa --- /dev/null +++ b/docs/Documentation/Wisdom.md @@ -0,0 +1,356 @@ +--- +title: Wisdom +parent: Documentation Development +nav_order: 7 +permalink: /Documentation/Development/Wisdom +--- + +# Wisdom --- Discord Knowledge Harvester +{: .no_toc } + +Three-phase tool that extracts technical knowledge from the twinBASIC Discord server and drafts documentation additions for human review. Source under [`wisdom/`](https://github.com/twinbasic/documentation/tree/main/wisdom). + +**Phase 1 --- Export:** fetch Discord messages to `data/raw/`. +**Phase 2 --- Process:** convert raw JSON to structured Markdown in `data/threads/`. +**Phase 3 --- Extract:** run Claude agents over the threads, draft documentation additions, produce a staging file for review. + +* TOC goes here +{:toc} + +## Prerequisites + +- **Node.js 18+** (uses native `fetch` and ES modules). +- **No external dependencies** --- the tool uses only Node.js built-in APIs. No `npm install` needed. +- **Discord token** --- for Phases 1 and 2. +- **Claude Code session** --- for Phase 3 (the extract workflow runs Claude agents). + +## Setup + +### Discord token + +Create a `wisdom/.token` file with your Discord token on one line: + +``` +xMTEz...your-token-here +``` + +Lines starting with `#` are ignored. Alternatively, set the `DISCORD_TOKEN` environment variable. + +The tool auto-detects whether the token is a bot token or a user token and applies the appropriate rate limits (bot: 5 req/s, 200/session; user: 2 req/s, 100/session). + +### Configuration + +`wisdom/config.jsonc` controls which channels to include, concurrency, and rate limits. The defaults target the twinBASIC Discord server. Override per-run via CLI flags: + +- `--guild ` overrides `guild_id` (or set `DISCORD_GUILD_ID`). +- `--concurrency `, `--rate-limit `, `--cap ` override the limits block. + +## Typical run + +The default flow is **incremental**: each phase tracks its own watermark so only content that has changed since the last successful run is processed. + +``` +node wisdom/wisdom.mjs export +node wisdom/wisdom.mjs process +node wisdom/wisdom.mjs extract +``` + +Then tell Claude: "Run the wisdom extract workflow." Claude invokes the workflow, writes the results, and runs the merge step, which grafts additions into `staging.md` and advances the watermark in `extract-state.json`. + +If the workflow completes but the merge step was not run (e.g. the session was interrupted), merge manually --- no agents run and no API costs are incurred: + +``` +node wisdom/wisdom.mjs extract --merge +``` + +**First run (no watermark yet):** when `extract-state.json` does not exist, the incremental filter has no baseline and treats every thread as new. For a large corpus this produces thousands of agent calls. Use `--since` to scope the first run to a manageable date range, or bootstrap the state file by running the extract workflow once with `--all` and then merging. + +**Date-scoped run** (bypass the watermark, filter by thread creation date instead): + +``` +node wisdom/wisdom.mjs export --since 2025-06-01 +node wisdom/wisdom.mjs process --since 2025-06-01 +node wisdom/wisdom.mjs extract --since 2025-06-01 +``` + +The `--since` mode writes to a sideband file (`staging-since-.md`) and does not touch the canonical `staging.md` or the watermark. + +The extract step automatically partitions large thread sets into batches of 200, so filtering is optional --- but `--since` and `--channel` reduce the number of threads analysed (and therefore agent invocations and API costs). + +## Reviewing staging.md + +`staging.md` is the long-lived review file. Each `## ` section is one proposed documentation addition, with structured metadata at the bottom (source thread IDs, confidence level, date range, optional reviewer note). Sections are grouped by target page and delimited by `---` lines. + +**Removing sections.** Delete any section that is not useful --- the removal is stable. If the source thread is unchanged on the next run, the watermark filter skips it entirely and the section stays gone. If the thread later receives new messages, the thread re-enters the pipeline and the agent may produce a fresh finding that accounts for the new context; it reappears with a `[REFINED?]` marker so the reviewer knows it is a revision of something already triaged. + +**Locking sections.** Append `[LOCKED]` to any section heading (e.g. `## docs/Reference/VB/Form/index.md · after-remarks [LOCKED]`) to freeze it. The merger skips locked sections on every future run, even if a new extraction produces a matching key. + +**Markers.** The merger may tag section headings with bracketed markers: + +- `[DUPLICATE?]` --- multiple threads produced overlapping findings for the same page and section. +- `[REFINED?]` --- this finding was previously emitted and removed from staging (presumably reviewed and integrated); the source thread has since changed and the agent produced a new version. +- `[LOCKED]` --- reviewer-applied; the merger will not touch this section. + +## CLI reference + +All commands run from the repository root via `node wisdom/wisdom.mjs [options]`. + +### Phase 1 --- Export + +Fetches messages from Discord channels and forum threads. + +``` +node wisdom/wisdom.mjs export +``` + +Outputs raw JSON under `wisdom/data/raw/`. Supports incremental runs --- a manifest tracks the highest message ID per channel, so re-running fetches only new messages. Use `--force` to re-fetch everything. + +| Flag | Effect | +|------|--------| +| `--since ` | Only fetch messages after this ISO 8601 date | +| `--channel ` | Restrict to one channel (repeatable) | +| `--dry-run` | Discover channels/threads; do not fetch messages | +| `--force` | Ignore manifest; re-fetch all history | + +When the session request cap is reached, the tool exits with code 2 --- re-run to continue where it left off. + +### Phase 2 --- Process + +Converts raw JSON into structured Markdown with YAML frontmatter. + +``` +node wisdom/wisdom.mjs process +``` + +Outputs one `.md` file per forum thread under `wisdom/data/threads//`, plus one per text channel. Each file has frontmatter (thread ID, channel, tags, reaction counts, `has_answer` flag) and a rendered message body with timestamps and author names. + +| Flag | Effect | +|------|--------| +| `--since ` | Only process threads created after this date | +| `--channel ` | Restrict to threads from this channel ID (repeatable) | +| `--force` | Regenerate all files (skip modification-time check) | + +### Phase 3 --- Extract + +Two steps: a CLI prep step, then a Claude Code workflow. + +#### Prepare + +``` +node wisdom/wisdom.mjs extract +``` + +Scans the processed thread files and the `docs/Reference/` directory, builds a sitemap of all documented symbols, and writes prep data to `wisdom/data/findings/`. No Claude agents run yet. + +- **200 threads or fewer:** writes a single `extract-prep.json`. +- **More than 200 threads:** partitions into batch files (`extract-batch-0.json`, `extract-batch-1.json`, ...) and writes an `extract-manifest.json` describing the batches. + +The prep step also writes two shared reference files that workflow agents read from disk: `package-summary.txt` (the documented-symbols list) and `page-index.json` (a compact lookup from package/symbol to documentation file path). + +| Flag | Effect | +|------|--------| +| `--since ` | Only analyse threads created after this date | +| `--channel ` | Restrict to threads from this channel **name** (repeatable) | +| `--min-confidence ` | Skip findings below `high`, `medium`, or `low` (default: `low`) | +| `--all` | Bootstrap: process all threads, ignoring state and channel filter | +| `--force` | Re-process threads even if their watermark matches state | +| `--dry-run` | Write the prep file but do not invoke the workflow | +| `--merge` | Graft extract-results-\*.json into staging.md and advance state (no agents) | + +#### Run the extraction workflow + +After the prep step finishes, tell Claude (in the same session or a new one): + +> Run the wisdom extract workflow. + +Claude detects whether a manifest or a single prep file was produced and handles either case: + +- **Single batch:** reads the prep file, invokes the workflow, writes the result file, runs the merge. +- **Multi-batch:** reads the manifest, runs one workflow per batch, saves each result to `extract-results-{i}.json`, then merges all results into `staging.md`. If interrupted, re-running picks up from the last incomplete batch. + +## Implementation + +### File layout + +``` +wisdom/ + wisdom.mjs Entry point --- CLI parser, runExport(), dispatch + config.mjs Load config.jsonc, apply CLI overrides + config.jsonc Server/channel/rate-limit configuration + + discord/ Phase 1 --- Discord API layer + api.mjs HTTP client, auth, rate-limiter, snowflake utilities + discover.mjs Channel + thread discovery, member fetch + messages.mjs Paginated message fetch, manifest (watermark) I/O + + process/ Phase 2 --- raw JSON to structured Markdown + thread.mjs Orchestrator: iterate raw files, apply filters, write .md + filter.mjs Keep only user-content message types (default + reply) + frontmatter.mjs Build YAML frontmatter from thread/channel metadata + render.mjs Render messages to Markdown (timestamps, replies, reactions) + slugify.mjs Thread-name-to-filename slug + + extract/ Phase 3 --- Claude-agent knowledge extraction + prep.mjs runExtract() (prep + batch partitioning) and runMerge() + state.mjs extract-state.json I/O, watermark comparison, emission log + sitemap.mjs Walk docs/Reference/, build package-summary + page-index + merger.mjs Parse/merge/serialise staging.md + schemas.mjs JSON Schema definitions for findings and additions + workflow.mjs Claude Code Workflow script (runs inside the Workflow tool) +``` + +### Entry point --- `wisdom.mjs` + +Parses `process.argv` into `{ command, flags }` and dispatches to one of three functions: `runExport()` (defined inline), `runProcess()` (imported from `process/thread.mjs`), or `runExtract()`/`runMerge()` (imported from `extract/prep.mjs`). + +Also defines `runConcurrent(items, concurrency, fn)` --- a simple worker-pool: spawns `min(concurrency, items.length)` async workers that pull from a shared index counter. If any worker throws `CapReachedError`, the pool drains without starting new items. + +### Discord API client --- `discord/api.mjs` + +`createClient(config)` returns `{ request, queryCount, tier, sessionCap }`. + +- **Auth detection.** Probes `/users/@me` with `Bot ` first; on failure, retries with the bare token (user-token auth). Sets `tier` to `'bot'` or `'user'`, which selects the rate-limit profile from config. +- **Rate limiter.** Enforces `requests_per_second` via a minimum inter-request delay. User-tier adds +/-20% jitter. Also reads Discord's `X-RateLimit-Remaining` / `X-RateLimit-Reset-After` headers and sleeps when a route bucket is exhausted. +- **Session cap.** Throws `CapReachedError` when `queryCount` reaches the configured cap. The caller catches this and exits with code 2; re-running resumes via the export manifest. +- **429 handling.** On HTTP 429, reads `retry_after` from the response body and recursively retries. +- **Snowflake utilities.** `snowflakeToTimestamp()` and `timestampToSnowflake()` convert between Discord snowflake IDs and Unix-millis timestamps using BigInt arithmetic (shift by 22 bits + the Discord epoch). + +### Phase 1 control flow + +1. **Discover** (`discord/discover.mjs`): fetch the full channel list from `/guilds/{id}/channels`, filter by type (text/forum) and exclude patterns. For forums, paginate `/channels/{id}/threads/archived/public` and (bot-only) `/guilds/{id}/threads/active`, deduplicate, and filter by `min_message_count`. +2. **Fetch members** (`discord/discover.mjs`): paginate `/guilds/{id}/members` (bot-only; user tokens get an empty map). Write `guild.json` and `members.json`. +3. **Build target list**: merge text channels and forum threads into a single list. Targets that previously returned 403 (tracked in `denied.json`) sort to the end. +4. **Fetch messages** (`discord/messages.mjs`): run targets through `runConcurrent`. For each target: + - Check manifest: if the target's highest-seen snowflake is recorded and the output file exists, skip (up-to-date). + - Call `fetchMessages(client, channelId, afterSnowflake)`: + - **Incremental** (afterSnowflake set): page forward with `?after=`, collecting new messages. + - **Full** (afterSnowflake null): page backward with `?before=`, collecting all history. + - Sort chronologically (ascending snowflake). + - Write `{ channel | thread, messages }` to `raw/channels/{id}.json` or `raw/threads/{id}.json`. + - Update manifest with `highestSnowflake(messages)` and flush to disk after each target. + +The export manifest (`raw/manifest.json`) is a flat `{ channelOrThreadId: highestSnowflake }` object. It governs incremental fetches --- on the next run, only messages newer than the stored snowflake are requested. + +### Phase 2 control flow + +1. Load `guild.json` to build `channelMap` (id to channel object) and `tagMap` (forum tag id to tag name). +2. Load `members.json` for author display-name lookup. +3. Iterate `raw/threads/*.json`: + - Skip if `--channel` filter does not match `thread.parent_id`. + - Skip if `--since` and thread creation date is before the cutoff. + - Skip if output `.md` exists and its mtime >= input mtime (unless `--force`). + - `filterMessages()` (`filter.mjs`): keep only type 0 (DEFAULT) and type 19 (REPLY). + - `shouldSkipThread()`: drop threads with one or fewer messages or all-bot authors. + - `buildFrontmatter()` (`frontmatter.mjs`): extract thread_id, title, channel, tags, created/archived timestamps, message_count, last_message_id (highest snowflake --- the Phase 3 watermark), reply_count, reaction aggregates, has_answer flag. + - `renderMessages()` (`render.mjs`): format each message as `**Author** _timestamp_` + content + attachments + reactions. Reply messages get a reply-to header with a truncated quote of the parent. + - Write `threads//--.md`. +4. Iterate `raw/channels/*.json` --- same pipeline but simpler frontmatter (channel_id, title, message_count). One output file per text channel. + +### Phase 3 control flow + +Phase 3 has three stages: prep (Node.js), workflow (Claude agents), merge (Node.js). + +#### Prep + +`runExtract()` in `extract/prep.mjs`: + +1. **Build sitemap** (`extract/sitemap.mjs`): walk `docs/Reference/`, parse frontmatter to collect `{ path, title, permalink, parent }` for every documented page. From this build two reference files: + - `package-summary.txt`: a compact `- Package > Module: Symbol1, Symbol2, ...` text listing. Read by Extract agents to know what symbols exist. + - `page-index.json`: a flat `{ "Package/Title": repoPath }` JSON object with bare-title shortcuts for unambiguous names. Read by Draft agents to resolve findings to file paths. + +2. **Load state** (`extract/state.mjs`): read `extract-state.json` --- a `{ processedThreads: { threadId: { last_message_id, message_count, emitted } } }` map. If absent, returns empty state (all threads appear new). + +3. **Filter threads**: scan every `.md` file under `data/threads/`, parse its frontmatter, and check `isThreadChanged(state, threadId, last_message_id, message_count)`. A thread passes the filter iff either field differs from the stored value, or the thread is not in state at all. + +4. **Partition**: if 200 or fewer threads, write a single `extract-prep.json`. Otherwise, sort by (channel, created), chunk into batches of 200, and write `extract-batch-{i}.json` files plus an `extract-manifest.json`. Each batch file contains thread file paths, a parallel file-size array (for the workflow's byte-budget grouping), config, and mode metadata. + +#### Workflow + +`extract/workflow.mjs` is a Claude Code Workflow script --- it runs inside the Workflow tool engine, not as a standalone Node.js process. The engine provides `agent()`, `pipeline()`, `parallel()`, `phase()`, `log()`, and an `args` global. + +1. **Parse args**: the batch file contents arrive as `args` (JSON). Extract thread paths, sizes, and config. + +2. **Group threads by byte size**: threads above 15 KB get a solo group. The rest are packed into groups of ~25 KB cumulative size. This reduces agent count by batching small threads together. + +3. **Two-stage pipeline** over the groups: + + **Stage 1 --- Extract.** One Sonnet agent per group. The agent reads the thread `.md` file(s) from disk, reads `package-summary.txt` to know the documented symbol surface, and returns `{ findings: [...] }` via structured output. Each finding has: `thread_path`, `package`, `symbol`, `kind` (gotcha/workaround/example/clarification/deprecation), `summary`, `detail`, `confidence` (high/medium/low), `date_earliest`, `date_latest`. + + **Stage 2 --- Draft.** One Sonnet agent per group (only if Stage 1 produced findings above the confidence threshold). The agent reads `page-index.json` to resolve each finding's package/symbol to a documentation file path, reads the resolved target page(s) to understand current content, and returns `{ additions: [...] }` via structured output. Each addition has: `thread_path`, `target_page` (repo-relative path or "UNMAPPED"), `section` (after-remarks/example/see-also/new-section), `draft` (exact Markdown to insert), `confidence`, `date_earliest`, `date_latest`, `reviewer_note`. + +4. **Assemble**: flatten all additions, extract thread IDs from file paths, and return `{ additions: [...] }`. + +The pipeline runs both stages without a barrier --- Stage 2 for group A starts as soon as group A's Stage 1 completes, while other groups may still be in Stage 1. + +#### Merge + +`runMerge()` in `extract/prep.mjs`, invoked by `node wisdom/wisdom.mjs extract --merge`. + +1. **Collect results**: read all `extract-results-*.json` files and concatenate their additions arrays. + +2. **Graft into staging.md** (`extract/merger.mjs` --- `graftAdditions`): + - Parse existing `staging.md` into `{ preamble, sections[] }`. The parser splits on `---` delimiter lines, then parses each chunk into heading (target_page + section + optional marker), body lines, and trailing meta lines (source threads, confidence, date range, reviewer note). + - For each addition, compute a match key: `(target_page, section, sorted finding_ids)`. + - **Key exists in staging.md** (and section is not `[LOCKED]`): replace the section body and meta in place. + - **Key not in staging, but in the emission log** (from `extract-state.json`): this was previously emitted, reviewed, and removed. Insert with a `[REFINED?]` marker. + - **Key not in staging and never emitted**: insert at the end of the target_page's contiguous group (or create the group). + - UNMAPPED sections sort before mapped sections. + - Atomic write: backup existing `staging.md` as `staging.md.bak`, write to `.tmp`, rename. + +3. **Advance state**: for each thread ID that contributed additions, call `recordEmission()` to store the current watermark values and the `(target_page, section, finding_ids)` tuples in the emission log. Then write `extract-state.json` atomically. + +### staging.md format + +``` +## docs/Reference/VB/Form/index.md · after-remarks + +> [!NOTE] +> Draft prose here... + +_Source threads: 9876543210 · confidence: high_ +_Date range: 2025-06-04_ +_Reviewer note: Verify against current .twin source before publishing._ + +--- +``` + +Each `## ` section is one addition. The heading encodes `target_page · section` plus an optional `[marker]` bracket (`DUPLICATE?`, `REFINED?`, `LOCKED`). Trailing italic lines are structured metadata the merger parses on re-read. Findings that do not map to any existing page use `UNMAPPED` as the target_page and are collected at the top of the file under an **Unmapped Findings** header. + +### Incremental watermarks + +Each phase has its own incrementality mechanism: + +| Phase | Watermark | Storage | Granularity | +|-------|-----------|---------|-------------| +| Export | Highest message snowflake per channel/thread | `raw/manifest.json` | Per target | +| Process | Output file mtime vs. input file mtime | Filesystem | Per file | +| Extract | `(last_message_id, message_count)` per thread | `findings/extract-state.json` | Per thread | + +The extract watermark uses two fields so it catches both new messages (snowflake advances) and message deletions (count decreases without snowflake change). + +## Data directory + +Almost everything under `wisdom/data/` is gitignored. The exception is `data/findings/staging.md`, which is tracked --- it is the long-lived review file and has value beyond any single session. + +``` +data/ + raw/ Phase 1 output + guild.json + members.json + manifest.json + channels/*.json + threads/*.json + threads/ Phase 2 output + / + --.md + .md + findings/ Phase 3 output + package-summary.txt shared: symbol list for Extract agents + page-index.json shared: symbol-to-doc-path lookup for Draft agents + extract-state.json per-thread watermark (advanced on merge) + extract-prep.json single-batch prep (<=200 threads) + extract-manifest.json multi-batch manifest (>200 threads) + extract-batch-{i}.json per-batch thread-path lists + extract-results-{i}.json per-batch workflow results + staging.md final review file (tracked in git) +``` diff --git a/docs/Documentation/index.md b/docs/Documentation/index.md index 0ff70c15..fb78cb6a 100644 --- a/docs/Documentation/index.md +++ b/docs/Documentation/index.md @@ -32,5 +32,6 @@ A single `build.bat` run executes `tbdocs` against a shared task DAG, dispatched - [Pipeline Stages](Development/Pipeline-Stages) --- complete interface reference: per-task signatures and per-module export tables, plus the scheduler-level concepts (flag bits, task lifecycle, SAB layout). - [Book Configuration](Development/Book-Configuration) --- `_book.yml` key reference for the PDF chapter manifest. - [Extending the Builder](Development/Extending) --- tutorial for adding a new pipeline task, markdown-it plugin, or render-worker sub-stage. +- [Wisdom](Development/Wisdom) --- the Discord knowledge harvester: a three-phase tool (export, process, extract) that mines the twinBASIC Discord for actionable technical knowledge and drafts documentation additions for human review. - [PDF Generation](Development/PDF-Generation) --- internals of the PDF renderer: `render-book.mjs`, paged.browser.js, and the pdf-lib shims. - [Library Patches](Development/Fixes) --- every modification to `paged.browser.js` and the `fast-*.mjs` pdf-lib shims: upstream problem, applied fix, and mechanism. diff --git a/wisdom/.gitignore b/wisdom/.gitignore new file mode 100644 index 00000000..f456c306 --- /dev/null +++ b/wisdom/.gitignore @@ -0,0 +1,5 @@ +data/* +!data/findings/ +data/findings/* +!data/findings/staging.md +.token diff --git a/wisdom/PLAN-1.md b/wisdom/PLAN-1.md new file mode 100644 index 00000000..b689ea9a --- /dev/null +++ b/wisdom/PLAN-1.md @@ -0,0 +1,244 @@ +# Wisdom — Phase 1: Export + +## Overview + +`wisdom` is a three-phase tool for harvesting knowledge from the twinBASIC Discord server and surfacing it as additions to the documentation. Phase 1 covers the export step: authenticating with the Discord HTTP API, discovering channels and forum threads, fetching full message history with pagination and rate-limit handling, and writing stable raw JSON output that Phases 2 and 3 consume. + +## Architecture summary + +| Phase | Description | Plan | +|-------|-------------|------| +| 1 — Export | Discord API → raw JSON | this file | +| 2 — Process | Raw JSON → structured `.md` files | PLAN-2.md | +| 3 — Extract | `.md` files → knowledge findings via agents | PLAN-3.md | + +Each phase is independently runnable. The boundaries are plain files on disk: `data/raw/` separates Phase 1 from Phase 2; `data/threads/` separates Phase 2 from Phase 3. Re-running Phase 2 with improved logic does not require touching the Discord API again. + +## Goals + +- Fetch all message history from designated channels and forum threads. +- Support incremental updates: re-running only fetches content newer than the last run. +- Handle Discord rate limits automatically, without manual throttling by the caller. +- Produce stable, reproducible JSON output that downstream phases can consume without network access. + +## Non-goals (Phase 1) + +- Filtering or classifying messages — Phase 2. +- Generating Markdown — Phase 2. +- Knowledge extraction — Phase 3. +- Any write operation (sending messages, adding reactions, modifying the server). + +--- + +## Authentication + +Discord supports two token types: + +- **Bot token** — `Authorization: Bot ` header. Requires the bot to be added to the server with `Read Message History`, `View Channels`, and `Read Messages` permissions. This is the ToS-compliant path and the recommended default. +- **User token** — `Authorization: ` header (no prefix). Authenticates as the caller's own Discord account and inherits the caller's access rights. Technically violates Discord ToS for automation; acceptable for one-off personal archival. + +Bot and user tokens are structurally identical — both are three-segment base64url strings (`[user_id].[timestamp].[signature]`). The "Bot " prefix exists only in the HTTP `Authorization` header, not in the stored token value. There is no way to distinguish token type by inspecting the string alone. + +**Auto-detection.** At startup, the tool probes `GET /api/v10/users/@me` with a `Bot` prefix. If the request succeeds and the response contains `bot: true`, the tool uses bot-token auth and bot-tier limits. If the `Bot`-prefixed request fails (401), it retries with a bare token (user-token auth) and applies user-tier limits. The probe counts toward the session query cap. + +Token is read from the `DISCORD_TOKEN` environment variable, with a fallback to `wisdom/.token` (a gitignored file). Lines starting with `#` are ignored, so the file can carry comments. It is never accepted as a CLI argument to avoid shell-history leakage. + +--- + +## Discovery: Guild → Channels → Threads + +``` +Guild +├── Text channels (type 0) regular discussion, announcements +├── Forum channels (type 15) Q&A, support, show & tell +│ ├── Active threads +│ └── Archived threads (paginated) +└── Voice, stage, categories skipped +``` + +**Step 1 — Channel list.** `GET /guilds/{guild_id}/channels` returns all channels in the server. Keep text channels (type 0) and forum channels (type 15); skip everything else. Channel objects are preserved in full — in particular, forum channels carry an `available_tags` array that Phase 2 needs to resolve thread tag IDs to display names. + +**Step 2 — Active threads.** `GET /guilds/{guild_id}/threads/active` returns all currently open threads across the entire guild. Filter to threads whose `parent_id` is one of the forum channels from Step 1. This endpoint is bot-only; user tokens receive a 403, which the tool handles by skipping active thread discovery and logging a warning. + +**Step 3 — Archived threads.** `GET /channels/{channel_id}/threads/archived/public?limit=100` paginates archived threads per forum channel. Paginate using the `has_more` flag and the `before` query parameter (ISO 8601 timestamp of the last thread's `archive_timestamp`, URL-encoded). Forum channels that return 403 (restricted access) are skipped with a warning. + +**Step 4 — Message fetch.** For each thread (and each in-scope text channel), fetch messages via `GET /channels/{channel_id}/messages?limit=100&before={snowflake}`, paginating until the response is empty or the incremental cutoff is reached. + +**Step 5 — Guild members.** `GET /guilds/{guild_id}/members?limit=1000&after={snowflake}` paginates the full member list. Server-specific nicknames (`nick` field) are not available on message author objects — this bulk fetch is the only way to obtain them. The response is written to `data/raw/members.json` as a map from user ID to `{ username, global_name, nick }`. On incremental runs, the member list is re-fetched in full (it is small relative to message history). + +Discovery (Steps 1–4) always runs in full on every invocation — it is the manifest that governs message pagination within known channels and threads, not channel or thread discovery. This ensures newly created threads are picked up on incremental runs. + +--- + +## Snowflake IDs and Incremental Updates + +Discord message IDs are Snowflakes — 64-bit integers that encode a millisecond timestamp: + +```js +const DISCORD_EPOCH = 1420070400000n +const timestampMs = (BigInt(snowflake) >> 22n) + DISCORD_EPOCH +``` + +Conversely, constructing a "synthetic message ID at time T" for use as a `before`/`after` cursor: + +```js +const snowflakeAtTime = (BigInt(timestampMs) - DISCORD_EPOCH) << 22n +``` + +The manifest file (`data/raw/manifest.json`) records the highest message Snowflake seen per channel or thread. The manifest is updated on disk as each channel or thread completes — not batched at the end of the run — so an interrupted export can resume where it left off. On subsequent runs, the `after` parameter is set to that stored value, so only newer messages are fetched. Passing `--force` ignores the manifest entirely and re-fetches from the beginning of history. + +--- + +## Rate Limiting + +Two layers of throttling apply: Discord's own per-route rate limits (communicated via response headers), and the tool's self-imposed limits that vary by token type. + +### Discord response headers + +| Header | Meaning | +|--------|---------| +| `X-RateLimit-Remaining` | Requests remaining in the current bucket window | +| `X-RateLimit-Reset-After` | Seconds until the window resets | +| `X-RateLimit-Bucket` | Opaque bucket identifier for this route | + +After each response, inspect `X-RateLimit-Remaining`. When it reaches 0, sleep `X-RateLimit-Reset-After` seconds before issuing the next request in that bucket. On a `429 Too Many Requests` response, sleep the `retry_after` value from the JSON body before retrying. + +### Tool-imposed limits + +The tool enforces its own rate and cap limits on top of Discord's per-route headers. Two tiers exist; the active tier is selected by auto-detection (see Authentication above). All values are configurable in `config.jsonc` under the `limits` key. + +| | User token (default) | Bot token (auto-detected) | +|---------------------|----------------------|---------------------------| +| Rate limit | 2 requests/second | 5 requests/second | +| Jitter | yes (randomized) | no (fixed interval) | +| Session query cap | 100 requests | 200 requests | + +**Rate limit.** The inter-request delay enforces the rate ceiling. For user tokens, the delay is randomized (jittered uniformly within the window — e.g. 400–600 ms for 2 req/s) to avoid a machine-regular cadence. For bot tokens, the delay is fixed (200 ms for 5 req/s) since bots are expected to make automated requests. + +**Session query cap.** The total number of Discord API requests (excluding retries after `429`) is capped per invocation. When the cap is reached, the tool stops fetching, writes the manifest with progress so far, and exits with a distinctive exit code and message. The next run resumes from where it left off via the manifest. This prevents a single session from making an unbounded number of requests — especially important during initial full-history exports of large servers, which will span multiple sessions. + +Both layers are additive: the tool never exceeds its own rate limit even if Discord's headers would allow a higher burst, and it never exceeds the session cap even if Discord's rate limit has remaining headroom. + +--- + +## Output Structure + +``` +data/raw/ + manifest.json { channelOrThreadId: highestSnowflakeString, ... } + guild.json raw response from GET /guilds/{id}/channels + threads/ + {thread_id}.json { thread: {...}, messages: [{...}, ...] } + channels/ + {channel_id}.json { channel: {...}, messages: [{...}, ...] } +``` + +`{thread_id}.json` includes the full thread object (title, creation time, forum tags, applied tag IDs) plus the complete ordered message array. Message objects are the raw Discord API shape — no transformation at this stage. The `guild.json` and `manifest.json` files are rewritten on every successful run. + +`data/` is covered by a local `.gitignore` entry — Discord message content is not committed to the repository. + +--- + +## Configuration file + +`wisdom/config.jsonc` is a committed JSONC (JSON with Comments) configuration file that controls discovery policy, rate limits, and export defaults. It is the right place for any persistent policy decision that would otherwise require a CLI flag on every run. JSONC is parsed by stripping `//` and `/* */` comments before `JSON.parse` — no external dependency required. + +```jsonc +{ + "guild_id": "927638153546829845", + + "channels": { + "exclude_patterns": [ + "github-.*", + "bot-.*", + "log-.*" + ], + "exclude_ids": [], + "include_types": ["text", "forum"] + }, + + "threads": { + "min_message_count": 1 + }, + + "export": { + "concurrency": 3 + }, + + // Tool-imposed rate and session limits, per token type. + // Auto-detection selects the active tier; see Authentication in PLAN-1. + "limits": { + "user": { + "requests_per_second": 2, // inter-request delay with uniform jitter + "session_query_cap": 100 + }, + "bot": { + "requests_per_second": 5, // fixed inter-request delay, no jitter + "session_query_cap": 200 + }, + // Randomized delay (seconds) before the auto-detection probe fires. + "probe_delay": [5, 10] + } +} +``` + +**`channels.exclude_patterns`** — regular expressions matched against the channel name (case-insensitive). Channels whose name matches any pattern are skipped entirely — their threads are never fetched. This is the primary mechanism for excluding noisy channels like `github-.*` (commit/PR feed bots) that contain no usable prose. + +**`channels.exclude_ids`** — a fallback for channels whose names are ambiguous or that lack a useful name pattern. Matched by exact Snowflake ID string. + +**`channels.include_types`** — limit discovery to `"text"` (type 0) and/or `"forum"` (type 15) channels. Both are included by default; remove `"text"` to skip regular text channels entirely if the server's knowledge is concentrated in forum threads. + +**`threads.min_message_count`** — threads with fewer messages than this threshold are not fetched. Default: 1 (skips only empty/deleted threads with zero messages). + +**`export.concurrency`** — default parallel fetch count, overridable at runtime via `--concurrency`. + +**`guild_id`** — the server's Snowflake ID. Defaults to the twinBASIC Discord server. Can be overridden on the CLI with `--guild`. + +**`limits.user`** / **`limits.bot`** — per-tier rate and session cap settings. `requests_per_second` controls the inter-request delay; user-tier delays are jittered, bot-tier delays are fixed. `session_query_cap` is the maximum number of counted API requests per invocation. + +**`limits.probe_delay`** — reserved for future use. The current implementation probes eagerly at startup. + +The config is loaded at startup and merged with any CLI flags; explicit CLI flags take precedence over config values. In particular, an explicit `--channel ` flag bypasses the config's `exclude_patterns` and `exclude_ids` rules entirely — requesting a specific channel by ID is an unambiguous include. + +--- + +## CLI + +``` +node wisdom/wisdom.mjs export [options] + + --guild Guild (server) ID [required, or DISCORD_GUILD_ID env var] + --channel Restrict to this channel ID (repeatable; default: all text + forum channels) + --since Fetch only content after this ISO 8601 date (overrides manifest) + --force Ignore manifest; re-fetch complete history + --out Output directory [default: wisdom/data/raw] + --concurrency Parallel channel/thread fetches [default: 3; Discord recommends ≤ 5] + --dry-run Discover channels and threads, print counts, do not fetch messages +``` + +During a live (non-dry-run) export, progress is logged to stderr: channels discovered, threads queued, messages fetched per thread, and a running total. This is the only user-facing feedback for what can be a long-running operation on a large server. + +--- + +## File layout (Phase 1) + +``` +wisdom/ + PLAN-1.md this file + PLAN-2.md process phase + PLAN-3.md extract phase + config.jsonc committed configuration (guild ID, channel exclusions, limits, defaults) + wisdom.mjs CLI entry point (subcommand dispatcher: export / process / extract) + config.mjs loads and validates config.jsonc (strips comments, parses, merges with CLI flags) + discord/ + api.mjs fetch wrapper: auth headers, rate-limit handling, retries + discover.mjs guild → channel list → active threads → archived threads → guild members + messages.mjs paginated message fetch with Snowflake cursor + .gitignore ignores data/ and .token + .token gitignored; optional token file (fallback when DISCORD_TOKEN is unset) + data/ gitignored + raw/ + members.json user ID → { username, global_name, nick } +``` + +`wisdom/` uses only Node.js built-in APIs (`fetch`, `fs`, `path`, `crypto`). No external dependencies, no `package.json`. The `wisdom.mjs` dispatcher is part of Phase 1's implementation scope — it owns argument parsing and delegates to the right module for each subcommand. diff --git a/wisdom/PLAN-2.md b/wisdom/PLAN-2.md new file mode 100644 index 00000000..d0e96b3d --- /dev/null +++ b/wisdom/PLAN-2.md @@ -0,0 +1,218 @@ +# Wisdom — Phase 2: Process + +## Overview + +Phase 2 reads the raw JSON produced by Phase 1 and converts it into structured Markdown files — one per forum thread (or per channel for non-forum text channels). The output is what the Claude agents in Phase 3 read. It must preserve enough conversational context for a reader (human or agent) to understand the thread without access to the original JSON, and surface the quality signals (reactions, tags, reply depth) that the extraction agents use to weight findings. + +## Goals + +- Produce one `.md` file per forum thread, with YAML frontmatter carrying metadata. +- Preserve thread context: replies are rendered under their parent messages when a `message_reference` is present, not only in chronological order. +- Surface quality signals — reactions, tag set, reply count — in frontmatter so Phase 3 agents can weight confidence. +- Stable output: re-running Process on the same input produces byte-identical output. + +## Non-goals + +- Relevance filtering or topic classification — Phase 3. +- Generating documentation prose — Phase 3. +- Any network access — Phase 1. + +--- + +## Input + +`data/raw/threads/{thread_id}.json` — the thread object plus ordered message array written by Phase 1. Structure: + +```json +{ + "thread": { "id": "...", "name": "...", "applied_tags": [...], ... }, + "messages": [{ "id": "...", "author": {...}, "content": "...", "reactions": [...], ... }] +} +``` + +`data/raw/channels/{channel_id}.json` — text channel messages, same shape but keyed by `channel`: + +```json +{ + "channel": { "id": "...", "name": "...", ... }, + "messages": [{ "id": "...", "author": {...}, "content": "...", "reactions": [...], ... }] +} +``` + +**Tag resolution.** Thread objects carry `applied_tags` as an array of Snowflake IDs. The human-readable tag names ("Answered", "WebView2", etc.) come from the parent forum channel's `available_tags` array, stored in `data/raw/guild.json` by Phase 1. Process loads the channel definitions at startup and builds a tag-ID-to-name lookup map for frontmatter generation. Text channels have no tags. + +**Display names.** Message author objects carry `username` and `global_name` but not the server-specific nickname. Phase 1 exports a separate `data/raw/members.json` mapping user IDs to `{ username, global_name, nick }`. Process resolves display names in priority order: `nick` (server nickname) > `global_name` > `username`. If members.json is empty (the bot lacked the Server Members Intent and got a 403), the resolver falls back to the `global_name` / `username` fields on each message's author object. + +--- + +## Thread structure + +A Discord forum thread has: + +- A **starter message** — the first message chronologically; its content is the original post. +- **Replies** — subsequent messages in chronological order. Each may carry a `message_reference` pointing to a specific earlier message. +- **Tags** — forum channel tags applied to the thread (e.g. "Answered", "Bug", "How-to"). These come from the thread object's `applied_tags` array, resolved against the parent channel's `available_tags`. + +For the common case of a linear conversation, chronological order suffices. When a reply's `message_reference` points to a non-immediately-preceding message, render it indented under its referenced parent so the exchange reads coherently. + +--- + +## Frontmatter schema + +```yaml +--- +thread_id: "1234567890123456789" +title: "How do I handle WebView2 NavigationCompleted?" +channel: "support" +channel_id: "9876543210987654321" +tags: ["Answered", "WebView2"] +created: "2024-03-15T10:23:00Z" +archived: "2024-03-18T14:05:00Z" # omit for active threads +message_count: 12 +last_message_id: "1234567890999999999" # snowflake of the newest message; Phase 3 watermark +reply_count: 11 # message_count minus the starter +starter_reactions: + "👍": 5 + "✅": 3 +top_reactions: # aggregate across all messages + "👍": 8 + "✅": 3 + "❤️": 1 +has_answer: true # true if "Answered" tag present +--- +``` + +Text channels use a simpler frontmatter — no tags, thread metadata, or answer signal: + +```yaml +--- +channel_id: "927638154192748606" +title: "general" +message_count: 4523 +top_reactions: + "👍": 120 + "❤️": 45 +--- +``` + +`has_answer` is the strongest single quality signal — it means a community member or maintainer marked the thread resolved. + +`last_message_id` is Phase 3's watermark. It carries the snowflake of the newest message in the thread (Discord snowflakes are monotonic and time-encoded), and pairs with `message_count` to let `extract` cheaply detect whether a thread has gained or lost content since its findings were last produced. The pair is content-derived, so it survives `git checkout`, cross-platform clones, filesystem syncs, and other operations that would invalidate a filesystem-mtime watermark. + +--- + +## Output structure + +``` +data/threads/ + {channel_name}.md text channel (one file per channel) + {forum_channel_name}/ + {thread_id}--{slugified-title}.md forum thread (one file per thread) +``` + +Forum threads nest under a subdirectory named after the parent forum channel. Text channels produce a single `.md` at the root of `data/threads/`. + +The `--` separator makes the ID and slug visually distinct without conflicting with slug hyphens. The ID prefix guarantees uniqueness even when two threads share similar titles after slugification. + +Slugification rules: lowercase; replace spaces and punctuation runs with a single `-`; strip characters outside `[a-z0-9-]`; trim leading/trailing `-`; cap at 80 characters. + +--- + +## Message rendering + +Each message renders as a block: + +```markdown +**DisplayName** _2024-03-15 10:23_ + +`DisplayName` is the resolved name from the priority chain described above (nick > global_name > username). + +The message body text, including any **markdown** Discord already +uses (bold, italics, inline code, fenced code blocks). + +> 👍×5 ✅×3 +``` + +Reactions appear as a blockquote line only when the message has at least one reaction with count ≥ 1. + +For a reply (message with `message_reference`), render a brief quoted excerpt of the referenced message before the reply body: + +```markdown +**DisplayName** _2024-03-15 10:31_ ↩ replying to **OtherUser** + +> The original snippet being replied to (first 120 characters)… + +The reply content. +``` + +Attachments are noted inline as `[attachment: filename.ext]` — do not attempt to download or embed. + +Code blocks in Discord messages (backtick-fenced, with or without a language hint) pass through verbatim. Single-backtick inline code also passes through. + +--- + +## Filtering at the Process stage + +Process applies only light, mechanical filtering — not relevance filtering (Phase 3's job): + +- **System messages** — skip messages whose `type` is not 0 (DEFAULT) or 19 (REPLY). Type 19 messages are Discord's "reply to a specific message" — they carry real user content and the `message_reference` that the renderer uses for reply threading. Types to skip include joins (7), pins (18), thread-created markers (21), etc. +- **Empty threads** — skip threads with zero non-system messages after the starter. For text channels, skip if zero non-system messages total. +- **Bot-only** — skip threads (or text channels) where every message author has `bot: true`. + +No content-based filtering here; that judgment belongs to the extraction agents. + +**Discord markdown pass-through.** Discord-specific markdown extensions — `||spoiler||` syntax, `>>> multiline quote` blocks, `` timestamps, custom emoji `:name:id` notation — pass through verbatim into the output `.md`. The output files are consumed by Claude agents (Phase 3), not rendered as standard Markdown, so fidelity to the original message content matters more than rendering correctness. + +--- + +## CLI + +``` +node wisdom/wisdom.mjs process [options] + + --in Input directory of raw JSON [default: wisdom/data/raw] + --out Output directory for .md files [default: wisdom/data/threads] + --channel Restrict to threads from this channel ID (repeatable) + --since Only process threads created after this ISO 8601 date + --force Regenerate all output files (default: skip threads whose raw JSON has not changed since the last process run, detected by comparing the raw file's mtime against the output .md's mtime) +``` + +The `--force` flag is useful after changing the rendering logic to regenerate all files consistently. + +--- + +## File layout (Phase 2 additions) + +``` +wisdom/ + PLAN-2.md + process/ + thread.mjs thread JSON → .md (orchestrates the steps below) + frontmatter.mjs builds the YAML frontmatter object from thread + tag metadata + render.mjs message array → Markdown body (handles replies, reactions, attachments) + slugify.mjs thread title → filename-safe slug + filter.mjs system-message and empty-thread filtering + data/ + threads/ gitignored +``` + +--- + +## Implementation notes + +**Determinism.** Tags in frontmatter are sorted alphabetically. Reaction maps are sorted by count descending, then by emoji name. Input files are processed in lexicographic filename order. Timestamps are normalized to `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds). These choices ensure byte-identical output on re-runs. + +**Slugification edge case.** If the thread title produces an empty slug after stripping (e.g. all-punctuation titles), the slug defaults to `untitled`. + +**Timestamps.** Message timestamps render as `YYYY-MM-DD HH:MM` in UTC (no seconds — matches the plan's examples). Frontmatter timestamps use full ISO 8601 with seconds. + +--- + +## Phase 1 changes made alongside Phase 2 + +Several resilience improvements to the export pipeline were implemented during Phase 2 development and are recorded here (PLAN-1 is unchanged): + +- **403 resilience.** The members endpoint, per-channel message fetch, and archived-threads endpoints all catch 403 errors and continue with a warning instead of crashing. +- **Denied-target tracking.** Targets that return 403 during message fetch are recorded in `data/raw/denied.json` with a timestamp. On subsequent runs, denied targets sort to the end of the fetch queue so accessible targets get the session budget first. `--force` clears the denied set. +- **Skip already-fetched targets.** Targets with both a manifest entry and an existing JSON output file are skipped entirely (no API call). This is critical for multi-session bulk exports — once a thread is fetched, it costs zero requests on every subsequent run. `--force` and `--since` override the skip. +- **CLI overrides.** `--rate-limit ` and `--cap ` flags override the tier-selected rate limit and session cap for the current run. diff --git a/wisdom/PLAN-3.md b/wisdom/PLAN-3.md new file mode 100644 index 00000000..3b97c278 --- /dev/null +++ b/wisdom/PLAN-3.md @@ -0,0 +1,418 @@ +# Wisdom — Phase 3: Extract + +## Overview + +Phase 3 runs Claude agents over the processed `.md` files from Phase 2, extracts actionable technical knowledge, maps each finding to the appropriate page in `docs/Reference/`, and drafts the exact prose to insert. The output is a human-readable staging file for review — no automatic commits or edits to the documentation. + +This phase is implemented as a **Workflow script** (`extract/workflow.mjs`) invoked through Claude Code's Workflow tool. It relies on agent orchestration infrastructure rather than being a standalone Node.js program. The schemas it uses (`extract/schemas.mjs`) are plain ES modules importable from Node.js as well, for any future standalone adaptation. + +## Goals + +- Read each thread `.md` and identify actionable technical findings: gotchas, workarounds, non-obvious behaviors, usage patterns, corrected misconceptions. +- Classify each finding by package and, where specific, by class, method, or property. +- Map each finding to the most appropriate page in `docs/Reference/`. +- Draft the exact Markdown prose to insert (a `> [!NOTE]` callout, a new example, a See Also entry, or a remarks paragraph). +- Produce a structured staging file for human review. +- Default to **incremental** operation: re-running after `export` + `process` only re-analyses threads whose Discord-side content has actually changed. +- Be **idempotent**: merging new findings into `staging.md` preserves pending review work, replaces stale findings whose source thread has grown, and does not duplicate findings the reviewer has already integrated into the docs. + +## Non-goals + +- Fetching from Discord — Phase 1. +- Converting JSON to Markdown — Phase 2. +- Automatic commits or edits to `docs/` without human review. +- Findings that duplicate content already on the target page. + +--- + +## Workflow design + +``` +data/threads/ Phase 2 output (frontmatter carries last_message_id + message_count) +data/findings/ + extract-state.json (watermark + emission log; advanced on successful merge) + package-summary.txt (read by Extract agents) + page-index.json (read by Draft agents) + ↓ +[prep: filter threads against extract-state.json; only changed threads survive] + ↓ +args: { threads, thread_sizes, config } ~19 KB per batch (JSON string) + ↓ +[group: pack threads into groups by byte budget (~25 KB target); threads >15 KB solo] + ↓ +[pipeline: one Extract agent per group] + reads: thread .md file(s) + package-summary.txt + returns: findings with thread_path provenance + ↓ +raw findings (FINDING objects, or null for irrelevant threads) + ↓ +[pipeline: one Draft agent per group with findings] + reads: page-index.json + target doc pages + returns: additions with thread_path provenance + ↓ +doc additions (DOC_ADDITION objects) + ↓ +[merge: graft into data/findings/staging.md; replace matching sections in place; + emit [REFINED?] markers for additions whose prior version was reviewed; + update extract-state.json] + ↓ +data/findings/staging.md Long-lived human-review file +``` + +The entire flow uses `pipeline()` — each group progresses from extraction to drafting independently, without waiting for other groups to complete. There is no cross-thread deduplication barrier; instead, the staging file groups additions by target page, and findings that appear in multiple threads are flagged with `[DUPLICATE?]` markers for human review. This trades some redundancy in the staging output for significantly faster wall-clock time. + +The Workflow `args` contains thread file paths, per-thread file sizes, and config — about 19 KB per batch. All bulk reference data (package summary, page index) lives in standalone files that agents read from disk. This keeps the invoking session's output cost negligible regardless of sitemap size. + +### Byte-budget grouping + +The median Discord thread is ~1.4 KB. Giving each one its own agent call wastes most of the overhead on the system prompt, tool schemas, and the package-summary read (9 KB, identical every time). The prep step emits `thread_sizes` — a parallel array of file sizes in bytes. The workflow packs threads into groups whose cumulative size approaches `TARGET_GROUP_BYTES` (25 KB); threads above `MAX_SOLO_SIZE` (15 KB) run solo. Each finding carries a `thread_path` field so provenance is preserved through both pipeline stages. + +Typical impact: 200 threads collapse to ~17 groups × 2 stages ≈ ~34 agents per batch (~3.5× fewer than the previous fixed-count grouping). Without `thread_sizes` (e.g. from older prep output), each thread is assumed to be 3 KB for grouping purposes. + +### Args serialisation + +The Workflow tool delivers `args` as a JSON string, not a parsed object. The script handles this with `JSON.parse(args)` on entry. + +### Model and StructuredOutput compliance + +Both agent calls use `model: 'sonnet'`. Opus is reliable but ~15× more expensive per call; the volume (~340 agent calls across 10 batches of 200 threads) makes that impractical. + +Sonnet requires explicit reminders in the prompt to call the `StructuredOutput` tool. Without them, ~50% of agents respond with plain text instead of using the tool, causing the pipeline item to fail. Both prompts end with an `IMPORTANT:` block instructing the agent to call StructuredOutput with the correct schema shape. + +--- + +## Schemas + +Schemas are inlined in `workflow.mjs` (workflow scripts cannot import modules). The canonical shapes: + +```js +EXTRACTION_SCHEMA = { + type: 'object', + properties: { + findings: { + type: 'array', + items: { + type: 'object', + properties: { + thread_path: { type: 'string' }, + // Exact file path of the source thread (verbatim from the agent's input list). + package: { type: 'string' }, + // e.g. "WebView2", "VBA", "CEF", "VB", "WinNativeCommonCtls" + symbol: { type: ['string', 'null'] }, + // Qualified name, e.g. "WebView2.Navigate", "Strings.Split". null = package-level. + kind: { enum: ['gotcha', 'workaround', 'example', 'clarification', 'deprecation'] }, + summary: { type: 'string' }, + detail: { type: 'string' }, + confidence: { enum: ['high', 'medium', 'low'] }, + date_earliest: { type: 'string' }, + date_latest: { type: 'string' }, + }, + required: ['thread_path', 'package', 'kind', 'summary', 'detail', 'confidence', + 'date_earliest', 'date_latest'], + }, + }, + }, + required: ['findings'], +} + +ADDITION_SCHEMA = { + type: 'object', + properties: { + additions: { + type: 'array', + items: { + type: 'object', + properties: { + thread_path: { type: 'string' }, + // Carried from the finding; the result assembly extracts the thread ID from this. + target_page: { type: 'string' }, + // Repo-relative path, e.g. "docs/Reference/WebView2/WebView2/index.md" + // Set to "UNMAPPED" when no existing page fits. + section: { enum: ['after-remarks', 'example', 'see-also', 'new-section'] }, + draft: { type: 'string' }, + confidence: { enum: ['high', 'medium', 'low'] }, + date_earliest: { type: 'string' }, + date_latest: { type: 'string' }, + reviewer_note: { type: ['string', 'null'] }, + }, + required: ['thread_path', 'target_page', 'section', 'draft', 'confidence', + 'date_earliest', 'date_latest'], + }, + }, + }, + required: ['additions'], +} +``` + +`thread_path` is the provenance key: it flows from extraction through drafting into the result assembly, where the thread ID is extracted from the filename (`THREAD_ID--slug.md`). This replaced the original design's `source_thread` (which relied on pipeline index for provenance) when thread grouping was introduced — multiple threads per agent call means the pipeline index no longer maps 1:1 to a thread. + +--- + +## Agent prompts + +### Per-thread extraction agent + +The agent reads two files from disk: +1. The thread `.md` file (path received from the pipeline). YAML frontmatter provides thread metadata (thread_id, channel, message_count, has_answer, tags); the body is the rendered conversation. +2. `wisdom/data/findings/package-summary.txt` — lists every documented package and its public symbols. + +Instructions: +- Return `null` if the thread is off-topic, purely social, unanswered, or contains no actionable technical content. +- Extract one `FINDING` object per distinct technical point — do not bundle unrelated points into one finding. +- Set `confidence: high` only when a twinBASIC maintainer confirms the behavior, or when at least two independent users agree. +- Set `confidence: low` for untested suggestions or anything hedged with "I think" / "maybe". +- Set `symbol` to the most specific qualified name possible. Use `null` only for genuinely package-level findings. +- `detail` must be self-contained prose — the reader has no access to the original thread. +- Do not extract findings that describe already-documented behavior. +- Prefer findings that are *surprising* or *not obvious* from the API surface. + +Very long threads (500+ messages) are passed to the agent in full — current models handle the context window comfortably. No chunking or truncation is applied. + +### Per-thread drafting agent + +The agent receives findings (from the extraction stage) as JSON in its prompt, then reads from disk: +1. `wisdom/data/findings/page-index.json` — a compact `{ "Package/Title": "docs/..." }` lookup for resolving findings to documentation pages. +2. Each resolved target documentation page — to understand current content and formatting. + +Page resolution follows a fallback chain: try `Package/Symbol`, then `Package/ClassName` (for dotted symbols like `WebView2.Navigate`), then `Package/ Package` for the package index. If none match, the agent sets `target_page` to `"UNMAPPED"`. + +Instructions: +- Produce one `DOC_ADDITION` object per logical insertion point. +- `draft` must conform to the site's Markdown conventions: `> [!NOTE]` for non-obvious behavioral clarifications, ` ```tb ` for code blocks, `--` not `—` for dashes. +- Do not reproduce the entire finding verbatim — write in the site's voice (plain English, third-person, active). +- If the finding maps to an example, produce a full code block with a one-line lead-in. +- For See Also additions, produce a `- [Symbol](relative-url) -- short description` line. +- Set `reviewer_note` when the draft requires verification against the `.twin` source or when it conflicts with anything currently on the page. +- When no existing page fits, set `target_page` to `"UNMAPPED"` — do not skip the finding. Include the package and symbol in `reviewer_note` so the reviewer can triage placement (create a new page, attach to a package index, etc.). + +--- + +## Quality signals used by agents + +From the frontmatter produced by Phase 2: + +| Signal | How used | +|--------|----------| +| `has_answer: true` | Increases confidence baseline for all findings in the thread | +| `starter_reactions` count | High reactions on the starter → the problem is common; worth documenting | +| `top_reactions` aggregate | High aggregate → thread content is broadly endorsed | +| `tags` includes "Bug" or "How-to" | Helps classify finding `kind` | +| `message_count` | Very short threads (≤ 3 messages) with no answer tag are treated with low confidence | + +--- + +## Staging output + +`data/findings/staging.md` is a human-readable review file. + +### Unmapped findings + +Not every finding maps to an existing documentation page. Cross-cutting gotchas, migration tips, IDE behavior, and general patterns may have no natural home in the current reference tree. These findings are drafted anyway and collected at the top of the staging file under an **Unmapped Findings** heading, tagged with their package and symbol for triage. The reviewer decides what to do with each: create a new page, attach to a package index, fold into an existing page the agent missed, or discard. + +```markdown +# Unmapped Findings + +## UNMAPPED · after-remarks + +> [!NOTE] +> COM threading in twinBASIC defaults to STA. Long-running COM calls on the +> UI thread block message processing — move them to a background thread. + +_Source threads: 1357924680135792468 · confidence: medium_ +_Date range: 2024-03-12 to 2024-03-14_ +_Reviewer note: Package: Core. Consider a new "Threading" guide page under docs/Reference/Core/._ + +--- +``` + +### Mapped additions + +Each proposed addition to an existing page renders as: + +```markdown +## docs/Reference/WebView2/WebView2/index.md · after-remarks + +> [!NOTE] +> Calling `Navigate` before the `EnvironmentReady` event fires silently drops the +> navigation request. Wait for `EnvironmentReady` before calling any navigation method. + +_Source threads: 1234567890123456789 · confidence: high_ +_Date range: 2024-06-01 to 2024-06-03_ +_Reviewer note: verify with WebView2 .twin source that no queuing occurs internally._ + +--- +``` + +When multiple threads produce findings for the same target page and section, the staging file groups them together and marks potential duplicates: + +```markdown +## docs/Reference/WebView2/WebView2/index.md · after-remarks [DUPLICATE? — see also thread 9876543210987654321] +``` + +The `[DUPLICATE?]` marker is a hint for the reviewer, not a guarantee — two threads may describe the same behavior from different angles, and the reviewer decides which draft to keep, merge, or discard. + +### Merge semantics + +`staging.md` is a long-lived review file. The merge step **does not overwrite** it on each run; it grafts new findings into place and replaces stale ones, preserving manual review state. + +**Match key.** For each new addition produced by the workflow, the merger computes `(target_page, section, sorted(finding_ids))` and looks for a section in `staging.md` with the same key. + +- **Match found, replace in place.** The existing section's body and reviewer-note metadata are replaced with the new draft; the section's position in its per-page group is preserved. +- **No match, insert.** A new section is added at the end of the target page's contiguous group (creating the group if the page is new to the staging file). +- **No match, but previously emitted.** If `extract-state.json` records that an addition with this key was emitted in a prior run but the corresponding section is no longer present in `staging.md` (presumably reviewed and integrated, or rejected), the new addition is appended with a `[REFINED?]` marker. The reviewer decides whether the docs page needs the updated content. + +**Reviewer escape hatch.** To prevent auto-replacement of a section mid-edit, the reviewer modifies its header — e.g., appending `[LOCKED]` or hand-editing the `finding_ids` list. The matcher then fails, the new finding inserts as a sibling, and the reviewer reconciles by hand. + +**Atomic writes.** The merger writes to a temp file in the same directory and renames it over `staging.md` so an interrupted run cannot leave the file in a half-written state. The previous `staging.md` is retained as `staging.md.bak` for one generation. + +**Per-run additions.** The per-batch `extract-results-{i}.json` files are preserved post-merge for diffing and post-hoc inspection. + +Example of a `[REFINED?]` section: + +```markdown +## docs/Reference/VB/Form/index.md · after-remarks [REFINED? -- previously processed; please diff against current docs page] + +> [!NOTE] +> ...updated draft body... + +_Source threads: 1512037273149898783 · confidence: high_ +_Date range: 2026-06-04 to 2026-06-12_ +_Reviewer note: This thread has gained new messages since the original finding was processed. Compare against the existing content on docs/Reference/VB/Form/index.md._ + +--- +``` + +--- + +## Incremental extraction + +The default behavior of `extract` is **incremental**: only threads whose Discord-side content has changed since the last successful merge are re-analysed. Combined with `export` and `process` (both already incremental — see PLAN-1 and PLAN-2), a routine update is just three commands with no flags: + +``` +node wisdom/wisdom.mjs export # pulls new messages since the manifest watermark +node wisdom/wisdom.mjs process # rewrites .md files whose raw input changed +node wisdom/wisdom.mjs extract # extracts from changed threads, grafts into staging.md +``` + +New findings merge into the existing `staging.md` without disturbing pending review work; refined versions of previously-emitted findings replace their predecessors in place per the [merge semantics](#merge-semantics) above. + +### State file + +`data/findings/extract-state.json` (gitignored) holds the per-thread watermark and the emission log: + +```json +{ + "version": 1, + "lastRun": "2026-06-04T15:35:00Z", + "processedThreads": { + "1512037273149898783": { + "last_message_id": "1512037273149898783", + "message_count": 4, + "emitted": [ + { "target_page": "docs/Reference/VB/Form/index.md", "section": "after-remarks" } + ] + } + } +} +``` + +- `last_message_id` and `message_count` are read from each thread's frontmatter (emitted by Phase 2) and compared against the file's current values. A thread is **included** for re-extraction iff either field differs from the stored value, or the thread isn't in `processedThreads` at all. +- `emitted` records the `(target_page, section)` pairs this thread has previously contributed to `staging.md`. The merge step uses this to detect "previously-emitted finding that's no longer in `staging.md`" and emit the `[REFINED?]` marker described above. +- State is updated only on **successful merge**. A workflow failure mid-pipeline leaves state untouched, so the next run retries the same threads. + +### Why `last_message_id`, not filesystem mtime + +Snowflakes are monotonic, encode a timestamp, and do not change under operations that touch the filesystem without changing the underlying content — `git checkout`, `git stash`, cross-platform clones, Dropbox/OneDrive sync, line-ending conversion, manual file copies. The frontmatter values are stable across all of those; mtimes are not. + +`last_message_id` alone catches additions (the snowflake advances). Pairing it with `message_count` catches deletions of the most recent message (where the snowflake regresses) and ordinary intra-thread deletions (where the count drops). Edits to non-last messages still slip through this check — an unavoidable tradeoff of watermark-vs-hash comparison; `--force` is the diagnostic escape. + +### Diagnostic flags + +| Form | Filter | State touched | Output | +|------|--------|---------------|--------| +| `extract` | watermark-driven (changed threads only) | yes, on merge | grafts into `staging.md` | +| `extract --since 2026-06-04` | by thread `created` date | no | sideband `staging-since-2026-06-04.md`, no merge | +| `extract --all` | all threads | yes, on merge | grafts into `staging.md` (refines existing; inserts new) | +| `extract --force` | watermark-driven plus the named channel(s) — pair with `--channel` | yes, on merge | grafts into `staging.md` | +| `extract --dry-run` | normal filter | no | prep file written, workflow not invoked | + +`--since`, `--all`, and `--force` are mutually exclusive as primary modes (combining them is a CLI error). `--since` is the diagnostic peek tool — it writes a sideband file so the reviewer can inspect what would change without disturbing the canonical staging file. + +--- + +## Data layout: shared files vs. batch files + +The prep step writes two kinds of output: **shared reference files** that every workflow batch reads, and **per-batch files** that contain only the thread list for that batch. + +### Shared files (written once) + +- `package-summary.txt` — one-per-line package/symbol summary (~10 KB). Read by every Extract agent. +- `page-index.json` — pretty-printed `{ "Package/Title": "docs/Reference/.../file.md" }` lookup (~95 KB, ~1400 entries). Read by every Draft agent for page resolution. Pretty-printed so agents can scan entries line-by-line rather than parsing a single long line. + +These files exist so that batch files stay small enough to pass as Workflow `args` without consuming excessive output tokens. Agents read them directly from disk. + +### Batch files + +- `extract-prep.json` — single-batch mode (≤200 threads): `{ threads: [path, ...], thread_sizes: [bytes, ...], config }`. +- `extract-batch-{i}.json` — multi-batch mode: same shape, one per batch. + +Each batch file is ~19 KB (file paths, sizes, and config). The invoking session reads it and passes the parsed JSON as Workflow `args`. `thread_sizes` is a parallel array used by the workflow's grouping logic; see "Byte-budget grouping" above. + +### Batch partitioning + +When the thread count exceeds 200, the prep step partitions the work into multiple batches. Each batch contains up to 200 threads. With byte-budget grouping, 200 threads collapse to ~17 groups × 2 stages ≈ ~34 agents per batch, well under the 1000-agent workflow cap. Threads are sorted by channel then creation date before partitioning. + +The invoking Claude session loops over batches sequentially, writing `extract-results-{i}.json` after each workflow completes. Batches whose result file already exists are skipped (resumability). After all batches complete, `node wisdom/wisdom.mjs extract --merge` reads the result files, grafts them into `staging.md` per the [merge semantics](#merge-semantics) above, and advances the watermark in `extract-state.json`. + +Cross-batch grouping and duplicate detection are part of the merge step — additions from different batches that target the same `(target_page, section)` but carry different `finding_ids` are grouped together in their per-page group and flagged with `[DUPLICATE?]` markers. Additions that share a full match key (same `target_page`, same `section`, same `finding_ids`) collapse into a single replace operation regardless of which batch they came from. + +--- + +## CLI + +``` +node wisdom/wisdom.mjs extract [options] + + --threads Input directory of processed .md files [default: wisdom/data/threads] + --out Output directory for findings [default: wisdom/data/findings] + --channel Restrict to threads from this channel name (repeatable) + --min-confidence Skip findings below this level: high | medium | low [default: low] + --since Diagnostic: filter by thread `created` date; do not touch state; write a sideband file instead of grafting + --all Bootstrap / re-baseline: process all threads, update state on merge + --force Re-process threads even if their watermark matches state (pair with --channel to scope) + --dry-run Write the prep file but do not invoke the workflow; state is not touched + --merge Graft extract-results-*.json into staging.md and advance state (no agents — pure Node.js) +``` + +The default mode (no `--since` / `--all` / `--force`) is **incremental**: the prep step filters threads against `extract-state.json` and only batches up those whose `last_message_id` or `message_count` has changed. + +`--since`, `--all`, and `--force` are mutually exclusive primary modes (combining them is a CLI error). + +`extract` spawns Claude agents via the Workflow tool and must be invoked from within a Claude Code session. `--merge` is the exception — it runs without agents, grafting batch results into the staging file and updating the state file. Standalone execution via the Anthropic SDK directly is a future option — Phase 3 implements the Workflow path first. + +--- + +## File layout (Phase 3 additions) + +``` +wisdom/ + PLAN-3.md + extract/ + workflow.mjs Workflow script (agent orchestration via Claude Code's Workflow tool) + merger.mjs parses staging.md, graft-merges DOC_ADDITIONs in place, atomic write + state.mjs reads / writes data/findings/extract-state.json (watermark + emission log) + sitemap.mjs globs docs/Reference/**/*.md, parses YAML frontmatter (title, permalink, parent) with a minimal built-in parser — no dependency on builder/ + prep.mjs CLI handler: applies state filter, scans threads, writes shared + batch files + data/ + findings/ gitignored + extract-state.json watermark + emission log; advanced on successful merge + package-summary.txt shared — package/symbol list for Extract agents + page-index.json shared — Package/Title → path lookup for Draft agents + staging.md long-lived review file (grafted, not overwritten) + staging.md.bak previous staging.md, one generation back + staging-since-{date}.md diagnostic sideband (from `--since`); never grafted + extract-prep.json single-batch prep (<=200 threads) + extract-manifest.json multi-batch manifest (>200 threads) + extract-batch-{i}.json per-batch thread paths + sizes + config + extract-results-{i}.json per-batch workflow results (preserved post-merge) +``` diff --git a/wisdom/README.md b/wisdom/README.md new file mode 100644 index 00000000..752b49da --- /dev/null +++ b/wisdom/README.md @@ -0,0 +1,9 @@ +# Wisdom — Discord Knowledge Harvester + +Documentation: [Wisdom](https://docs.twinbasic.com/Documentation/Development/Wisdom) on the docs site. + +Design documents: + +- [PLAN-1.md](PLAN-1.md) — Phase 1 (export) +- [PLAN-2.md](PLAN-2.md) — Phase 2 (process) +- [PLAN-3.md](PLAN-3.md) — Phase 3 (extract) diff --git a/wisdom/config.jsonc b/wisdom/config.jsonc new file mode 100644 index 00000000..5e0a49eb --- /dev/null +++ b/wisdom/config.jsonc @@ -0,0 +1,42 @@ +{ + // twinBASIC Discord server + "guild_id": "927638153546829845", + + "channels": { + // Channel name patterns to exclude (case-insensitive regex). + "exclude_patterns": [ + "github-.*", + "bot-.*", + "log-.*" + ], + // Channel IDs to exclude by exact match. + "exclude_ids": [], + // Channel types to include: "text" (type 0) and/or "forum" (type 15). + "include_types": ["text", "forum"] + }, + + "threads": { + // Skip threads with fewer messages than this. + "min_message_count": 1 + }, + + "export": { + // Parallel channel/thread fetches (Discord recommends <= 5). + "concurrency": 3 + }, + + // Tool-imposed rate and session limits, per token type. + // Auto-detection selects the active tier; see Authentication in PLAN-1. + "limits": { + "user": { + "requests_per_second": 2, + "session_query_cap": 100 + }, + "bot": { + "requests_per_second": 5, + "session_query_cap": 200 + }, + // Randomized delay (seconds) before the auto-detection probe fires. + "probe_delay": [5, 10] + } +} diff --git a/wisdom/config.mjs b/wisdom/config.mjs new file mode 100644 index 00000000..f1109bc6 --- /dev/null +++ b/wisdom/config.mjs @@ -0,0 +1,53 @@ +import { readFileSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +function stripJsonComments(text) { + let result = '' + let i = 0 + let inString = false + let escaped = false + while (i < text.length) { + if (inString) { + result += text[i] + if (escaped) escaped = false + else if (text[i] === '\\') escaped = true + else if (text[i] === '"') inString = false + i++ + } else if (text[i] === '"') { + inString = true + result += text[i++] + } else if (text[i] === '/' && text[i + 1] === '/') { + i += 2 + while (i < text.length && text[i] !== '\n') i++ + } else if (text[i] === '/' && text[i + 1] === '*') { + i += 2 + while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++ + i += 2 + } else { + result += text[i++] + } + } + return result +} + +export function loadConfig(cliFlags = {}) { + const raw = readFileSync(join(__dirname, 'config.jsonc'), 'utf-8') + const config = JSON.parse(stripJsonComments(raw)) + + if (cliFlags.guild) config.guild_id = cliFlags.guild + if (cliFlags.concurrency != null) config.export.concurrency = Number(cliFlags.concurrency) + if (cliFlags.rateLimit != null) config.limits._rateLimit = Number(cliFlags.rateLimit) + if (cliFlags.cap != null) config.limits._cap = Number(cliFlags.cap) + if (cliFlags.out) config.export.out = cliFlags.out + + if (!config.guild_id) { + const envGuild = process.env.DISCORD_GUILD_ID + if (envGuild) config.guild_id = envGuild + else throw new Error('Guild ID required: set guild_id in config.jsonc, pass --guild, or set DISCORD_GUILD_ID') + } + + return config +} diff --git a/wisdom/data/findings/staging.md b/wisdom/data/findings/staging.md new file mode 100644 index 00000000..29f15b2f --- /dev/null +++ b/wisdom/data/findings/staging.md @@ -0,0 +1,15553 @@ +# Wisdom Extract -- Staging + +# Unmapped Findings + +_These findings do not map to an existing documentation page. The reviewer decides: create a new page, attach to a package index, fold into an existing page, or discard._ + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> Debug output in twinBASIC is processed asynchronously. Messages written via `Debug.Print` may not appear in the output window immediately --- the display can lag behind the actual execution point when stepping through code. This means an error may appear to have occurred later than it actually did, or output from a statement may not yet be visible when the next statement is reached in the debugger. + +_Source threads: 1053774015275216966 · confidence: high_ +_Date range: 2022-12-23_ +_Reviewer note: Package: VB (context: UserControl debugging), Symbol: none (general twinBASIC debugger behavior). No existing page covers Debug.Print output behavior. Consider placing on a twinBASIC-specific debugging or IDE behavior page if one is created, or on the twinBASIC Additions page (docs/Reference/twinBASIC-Additions.md) as a known behavioral difference._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> In IDE BETA 252, pasting multi-line code that contains line-continuation characters (`_`) and then editing the pasted content (for example, removing C++ type suffixes or address-of operators) may cause the editor to re-insert previously deleted text when focus moves away from the affected line. Each attempt to delete the re-inserted text is undone when the cursor leaves that line. The workaround is to select and delete all text after the last valid token on the affected line and retype that content from scratch. + +_Source threads: 1076141090513625129 · confidence: medium_ +_Date range: 2023-02-17_ +_Reviewer note: Package: Core, Symbol: null (IDE editor bug). This finding describes a version-specific editor bug observed in BETA 252 where editing pasted code with line-continuation characters causes re-insertion of deleted text. There is no existing reference page for IDE editor bugs or workarounds. Consider whether this is still present in current builds before publishing; if fixed, discard. If still reproducible, a new 'Known Issues' or 'Editor Caveats' page under docs/Features/Compiler-IDE/ would be the appropriate home._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> When compiling very large projects (for example, large VB6 codebases being ported to twinBASIC), the compiler may abort with the message: **TWINBASIC COMPILER ERROR: codegen memory block is fragmented. Please restart the compiler.** This indicates that the code-generation memory allocator has run out of contiguous space for the current session. The fix is to close and restart the twinBASIC IDE, then recompile. + +_Source threads: 1111087497389441095 · confidence: medium_ +_Date range: 2023-05-25_ +_Reviewer note: Package: Core, symbol: null. No existing page covers compiler-internal errors of this kind. Possible homes: a new 'Known Compiler Limitations' or 'Troubleshooting' page, or an expansion of docs/Features/Compiler-IDE/Compiler-Warnings.md (currently covers only design-time warnings, not hard abort errors). Reviewer should decide placement before adding. Observed in beta 314._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Prior to BETA 366, placing a line-continuation character (`_`) immediately before a chained method call's leading dot caused a compile error. VBA allows splitting `obj.Method1(). _` followed by `.Method2()` on the next line; twinBASIC required the continuation to start after a complete sub-expression. This was fixed in BETA 366. When porting VBA code that uses mid-chain line continuation, consolidate the chain onto fewer lines if the project targets an earlier release. + +_Source threads: 1134644019968868532 · confidence: high_ +_Date range: 2023-07-29 to 2023-07-31_ +_Reviewer note: No existing page covers line-continuation syntax. Possible placements: a new Core/Line-Continuation.md page, or a note on the twinBASIC-Additions.md or a VBA-compatibility page. Since the bug is fixed in BETA 366 the note has limited ongoing value; reviewer should decide whether to add it at all and where. Package is 'Core', symbol is null._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> The VBP importer requires Windows-style CrLf line endings in the `.vbp` file. A project file with Unix-style LF-only line endings produces no output and no error --- the import appears to succeed but nothing is added to the project. This commonly happens when a VB6 project is downloaded from a GitHub repository where git's line-ending normalization has stripped the carriage-return characters. Convert the file's line endings to CrLf before importing (for example, with a text editor that supports line-ending conversion, or with the `dos2unix`/`unix2dos` tools). + +_Source threads: 1139937031775072307 · confidence: high_ +_Date range: 2023-08-12_ +_Reviewer note: Package Core, symbol null. No page in the page-index covers VBP import gotchas directly. The closest existing page is docs/Miscellaneous/FAQs.md, which has a 'vb6-import' section (id='vb6-import') that already describes the import wizard. Suggest inserting this NOTE callout inside that
block, after the existing import screenshots, or creating a new FAQ entry. Reviewer should confirm the LF-only behavior is still current and decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> `.bas` files downloaded from GitHub using the GitHub desktop client may have CRLF line endings converted to LF by default. twinBASIC (like VB6) requires CRLF line endings in `.bas` files; LF-only files cause persistent parse errors that reappear after saving. To avoid this, download `.bas` files through the GitHub web interface rather than the desktop client, or adjust the desktop client's line-ending settings before downloading. + +_Source threads: 1140151535217680436 · confidence: high_ +_Date range: 2023-08-13 to 2023-08-14_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This is a workflow gotcha about importing .bas files from GitHub. No dedicated import/migration page exists in the reference section. Possible placement: a new 'Importing VB6 Modules' or 'Migration' page under docs/Features/, or as a note on the Features/Language/Module-Organization.md page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The message **NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code}** means an access violation occurred outside the user's BASIC source. The two main causes are: a native library used by the project crashed, or the twinBASIC compiler or debugger itself crashed. A secondary symptom reported by community members is that the message can appear when a compilation or linker error was not displayed in the error panel, leaving the executable in an incomplete state. + +_Source threads: 1155914737331228763 · confidence: high_ +_Date range: 2023-09-25 to 2023-09-27_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This describes an IDE/runtime error message rather than a language symbol. Consider adding to a Troubleshooting or Glossary page, or to docs/Reference/Glossary.md._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a third-party ActiveX control's type library can be added both as a **Component** and as a **Reference**, VB6 automatically appends `Ctl` to the library name used as a Component (for example, `ExLVwLibUCtl` instead of the raw name `ExLVwLibU`). twinBASIC reads the raw type library name without the suffix. Projects ported from VB6 will show qualified-reference errors for every identifier that uses the VB6-suffixed name. The workaround is to rename the library reference in the twinBASIC project settings to add the missing `Ctl` suffix manually. A permanent fix is planned. + +_Source threads: 1175117587135352835 · confidence: high_ +_Date range: 2023-11-17_ +_Reviewer note: UNMAPPED: Package=VB, Symbol=null. This is a VB6 porting / ActiveX component compatibility gotcha with no existing dedicated page. Possible placements: a new VB6 migration/compatibility page, the VB Package index (docs/Reference/VB/index.md), or a Features/Project-Configuration page. Reviewer should decide placement before adding._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> In twinBASIC IDE builds around BETA 423 (November 2023), each press of the **Delete** key caused the IDE's memory usage to rise by up to 50 MB per keypress, with the memory not released between presses. On large projects, repeated deletions could exhaust available RAM and produce an out-of-memory white screen. Any unsaved changes were lost on recovery. No workaround was available at the time this was reported. + +_Source threads: 1177081562035142716 · confidence: medium_ +_Date range: 2023-11-23_ +_Reviewer note: Package: Core, Symbol: null. Pure IDE performance bug (Delete key memory spike, BETA 423 era). No existing reference page covers IDE performance or known bugs. Consider creating a known-issues or IDE-behavior page, or attaching to the twinBASIC Additions / release notes page if one exists. Verify whether this was fixed and in which BETA before publishing._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If a project file contains `mscorlib` listed twice under References, loading the project causes the IDE to stall indefinitely at the "please wait" stage. To recover: start twinBASIC, close the start screen, change the build target to **safemode** (instead of Win32 or Win64), then open the affected project. In the References panel both entries appear; unticking either one removes both. Re-add the single correct `mscorlib` reference and save. The project will then open normally. This issue was present in BETA 435 and fixed in BETA 444, but the safemode recovery procedure applies to any project file with a duplicate reference entry. + +_Source threads: 1203300558186876989 · confidence: high_ +_Date range: 2024-02-03 to 2024-02-10_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. There is no existing page covering project References management or the safemode build target. This finding may belong on a future 'Project Settings' or 'Troubleshooting' page, or on an IDE-level page if one is added._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> **Duplicate-definition error after re-importing a class module.** Deleting a class module from the Sources tree and immediately re-importing it under the same name can produce a `duplicate definition in the current scope` or `'' is ambiguous` error in the Problems pane. The incremental compiler retains the deleted module in its state until the compiler is restarted. Saving the project before re-importing does not prevent this. The same symptom appears when dragging source files from a subfolder into the top-level Sources folder. Use the **Restart Compiler** button (or the equivalent menu item) to clear the error. + +_Source threads: 1208787275467587604 · confidence: high_ +_Date range: 2024-02-18_ +_Reviewer note: Package: Core, symbol: null. This is an IDE/compiler workaround with no matching page in the page index. Suitable placement would be an IDE troubleshooting or known-issues section, or a dedicated page covering incremental compilation behaviour. The bug was reported as a regression introduced after release 423 — check whether a later release fixed it before publishing._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC project files (`.twinproj`) embed all referenced package definitions directly into the save file. A project that references large packages can produce a `.twinproj` file of 10 MB or more. The compiled executable is much smaller: most package code is not compiled in, so a typical executable is a few MB, and a minimal program with no package references can be as small as 4 KB. Support for external package references --- so packages do not have to be embedded in the project file --- was planned before v1.0. + +_Source threads: 1219001427272400976 · confidence: high_ +_Date range: 2024-03-17_ +_Reviewer note: Package: VB, symbol: null (project file embedding behavior). No suitable existing page found -- this is IDE/project file behavior, not specific to the VB package. Consider placing on a new or existing IDE documentation page (e.g. docs/IDE/Project Settings.md under a 'Package references' or 'File size' note) or a dedicated project file page. The VB Package index (docs/Reference/VB/index.md) is not a good fit._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Prior to twinBASIC BETA 562, passing a `Variant` array to `DispCallFunc` could produce an `ACCESS_VIOLATION` crash or a silent no-effect result (for example, `IStream::Read` returning `S_OK` but reporting 0 bytes read). The cause was that the runtime did not strip `VT_BYREF` indirections from copied array elements before invoking the target function. The workaround for earlier versions was to copy each array element through `CVar()` before building the parameter array. This was fixed in BETA 562. + +_Source threads: 1243307409762553897 · confidence: high_ +_Date range: 2024-05-23 to 2024-06-20_ +_Reviewer note: Package: VBA, symbol: Interaction.DispCallFunc. No dedicated page exists for DispCallFunc in the docs. Reviewer should decide whether to create a DispCallFunc page (it is a low-level VB/VBA COM-dispatch API distinct from CallByDispId) or attach this note to a related existing page such as the Interaction module index._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> In twinBASIC BETA 543, loading `.jpg` or `.bmp` image files into an **Image** or **PictureBox** control caused a run-time error. This regression was fixed in BETA 544. Projects targeting BETA 543 should use `.png` or `.ico` files instead. + +_Source threads: 1243998176625688576 · confidence: high_ +_Date range: 2024-05-25_ +_Reviewer note: Package: VB. Affects the Image and PictureBox controls (docs/Reference/VB/Image/index.md and docs/Reference/VB/PictureBox/index.md). This is a historical regression fixed in a very old version (BETA 544); reviewer should decide whether documenting it is still useful or whether it can be omitted entirely. If kept, add the note to both Image and PictureBox pages, or to a release-notes/changelog page if one exists._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> **Known bug --- `Insert` overwrites instead of shifting.** As of early 2025, the twinBASIC `ArrayList` drop-in replacement has a defect in its `Insert` method: calling `myAL.Insert index, value` overwrites the element at `index` rather than shifting existing elements up to make room. An extraneous empty or zero element is also appended at the end of the list after each `Insert` call. For example, starting from `{10, 20, 30, 40, 50, 60, 70}`, calling `myAL.Insert 3, 84` produces `{10, 20, 30, 84, 50, 60, 70, Empty}` instead of the expected `{10, 20, 30, 84, 40, 50, 60, 70}`. The issue has been filed on the twinBASIC GitHub repository. As a workaround, rebuild the list manually by reading elements into a temporary array, inserting at the desired position, and reassigning. + +_Source threads: 1336975989464633344 · confidence: medium_ +_Date range: 2025-02-06_ +_Reviewer note: VBA/ArrayList -- no ArrayList page exists in the docs. Once an ArrayList reference page is created, this bug note should be placed in its after-remarks section. The affected package appears to be the contributed ArrayListLib (sources at .claude/worktrees/elegant-elion-ba6399/indexer/.packages/contributed/ArrayListLib/). Verify whether the bug has since been fixed in the twinBASIC repository before publishing._ + +--- + +## UNMAPPED · new-section + +**[ComExport] on Sub API Declare -- BETA 801 bug fix** + +Before BETA 801, applying `[ComExport]` to a `Sub` (void) API `Declare` statement in an ActiveX DLL standard module incorrectly populated the type library entry with `VT_EMPTY(0)` instead of `VT_VOID`. VB6 clients raised "Unsupported Variant type" when attempting to use the declaration. The workaround of adding `[PreserveSig(False)]` was unsafe: it caused the void return to be treated as an HRESULT, so any non-zero value left in the return register by the callee was interpreted as a failing HRESULT and raised a runtime error. After upgrading to BETA 801 or later, remove any `[PreserveSig(False)]` added as a workaround for this issue. + +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-06-10_ +_Reviewer note: UNMAPPED: package=Core, symbol=[ComExport]. No dedicated page exists for the [ComExport] attribute in docs/Reference/Attributes.md -- the attribute is not listed there. Consider adding a ComExport section to Attributes.md. The bug-fix note (BETA 801) should be incorporated once that section exists._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Classic `HIWORD(CLng(wParam))` calls overflow in 64-bit builds when `wParam` (type `LongPtr`) carries a large value such as `&HFF000000^`. The correct approach is to accept `LongPtr` as the input type and extract the high word with a right-shift: +> +> ```tb +> Public Function HIWORD(ByVal Value As LongPtr) As Long +> Return CLng((Value >> 16) And &HFFFF&) +> End Function +> ``` +> +> For a signed high word (required for the wheel delta, which can be negative), use a UDT overlay instead of casting: +> +> ```tb +> Private Type SignedWords +> LoWord As Integer +> HiWord As Integer +> End Type +> +> ' Read wParam's high word as a signed Integer: +> Dim delta As Integer +> delta = CType(Of SignedWords)(VarPtr(wParam)).HiWord +> ``` +> +> A 32-bit fallback can use the traditional `CLng` approach under conditional compilation (`#If Win64 Then ... #Else ... #End If`). + +_Source threads: 1350983287996153966 · confidence: high_ +_Date range: 2025-03-17 to 2025-03-22_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This is a 64-bit `wParam` HIWORD/LOWORD gotcha with no obvious single-page home. Candidates: a new 64-bit porting page under docs/Reference/Core/ or docs/Features/, or the existing Declare page as an extended note on 64-bit message-parameter handling. A reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a project defines a UDT or class with the same name as a type in a built-in package (for example, a local `STGMEDIUM` alongside the one from `olelib`), the compiler's type resolver may select the wrong definition. Qualifying the type name with its library prefix removes the ambiguity: +> +> ```tb +> Dim medium As olelib.STGMEDIUM +> ``` +> +> This pattern applies to any well-known COM type that a project redefines locally, including `FORMATETC` and `VARIANT`. + +_Source threads: 1352737500606500965 · confidence: high_ +_Date range: 2025-03-21 to 2025-03-22_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This is a general name-disambiguation tip with no obvious single-page home. Candidates: the Declare page, the Type statement page, or a new 'Name disambiguation' remark on the twinBASIC Additions page. A reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If the twinBASIC IDE compiler crashes or restarts three times in rapid succession, the IDE switches to safe mode automatically. In safe mode, the form designer is not available --- only code and text viewing work --- and the sync progress bar may appear stuck. To exit safe mode, switch the target architecture dropdown in the IDE toolbar (Win32 / Win64) to the other option. If that does not restore normal operation, a full IDE restart is required. + +_Source threads: 1361637218992656475 · confidence: high_ +_Date range: 2025-04-16_ +_Reviewer note: UNMAPPED: package VB, symbol null. This describes IDE-level behavior (safe mode triggered by repeated compiler crashes) rather than a class or control. Candidate pages: a general IDE troubleshooting page under docs/, or docs/Reference/VB/index.md as a note. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> After adding an OCX control to a twinBASIC project for the first time, save the project and then restart the compiler using the circular-arrow button in the IDE toolbar (next to the Win32/Win64 architecture dropdown). The control should then appear in the toolbox. A full IDE restart achieves the same result but is slower. + +_Source threads: 1361681088556175571 · confidence: medium_ +_Date range: 2025-04-15_ +_Reviewer note: UNMAPPED: package VB, symbol null. This is an IDE workflow tip about adding OCX controls, not specific to any class. Candidate page: docs/Reference/VB/index.md or a general IDE setup / getting started page. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Function overloads are not supported in ActiveX DLL or OCX class modules. COM type libraries do not allow overloaded method names, so attempting to compile an ActiveX project with overloaded methods in a public class produces errors such as `[TYPELIB] failed to create typelibrary interface procedure names` and `[TYPELIB] failed to set coclass inheritance`. Overloads work normally in standard EXE class modules. This is a COM limitation, not a twinBASIC bug. + +_Source threads: 1361995709657645056 · confidence: high_ +_Date range: 2025-04-16 to 2025-04-17_ +_Reviewer note: UNMAPPED: package VB, symbol null. This covers a COM/ActiveX compilation constraint. Candidate pages: docs/Reference/Core/Class.md, docs/Reference/Core/CoClass.md, or a new ActiveX authoring note on docs/Reference/VB/index.md. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Under certain conditions, Ctrl+S or the Save menu item can appear to succeed without writing the project to disk. The DEBUG CONSOLE pane shows `saving to disk [DONE]` when a save completes; if no such message appears after saving, the save has silently failed. The cause is typically one stuck open editor tab. The workaround is to right-click any tab and choose **Close all**, which releases the stuck state and allows a normal save. +> +> As of BETA 769, the IDE displays an explicit failure dialog when a save does not complete within a timeout, making silent failures visible. + +_Source threads: 1371946986055401503 · confidence: high_ +_Date range: 2025-05-13 to 2025-05-14_ +_Reviewer note: UNMAPPED -- package: tbIDE, symbol: null. This finding describes IDE save behavior (not the addin SDK API). No existing page covers IDE editor tips or known-issue workarounds. Consider adding a 'Tips and Known Issues' page under docs/Documentation/ or docs/Reference/tbIDE/, or appending to an existing 'Working with the IDE' page if one is created._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a Win32 API parameter is declared `ByVal LongPtr` (as in many WinDevLib / WDL declarations), passing a **String** variable directly is accepted by the compiler but fails at runtime. Pass `StrPtr(buffer)` instead. The same pattern applies wherever a declaration uses `LongPtr` in place of a higher-level type: use `StrPtr()`, `VarPtr()`, or `ObjPtr()` as appropriate. + +_Source threads: 1380262133283164180 · confidence: medium_ +_Date range: 2025-06-05 to 2025-06-06_ +_Reviewer note: Package: VBA, symbol: null. Finding is about a third-party library (WinDevLib/WDL) pattern, not a specific VBA symbol. A good home would be the Declare statement page (docs/Reference/Core/Declare.md) or a general interop/type-safety tips section. Reviewer should decide placement and verify the draft is appropriate for the chosen page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC performs a full background recompile on every edit --- including changes inside comments --- to keep IntelliSense, hover information, and live diagnostics current. Partial recompilation is planned but not yet available. On large projects or slow machines this can cause the diagnostics pane to update frequently; if the IDE save operation times out (the compiler must respond within 5 seconds), the timeout dialog appears and the save can be retried. + +_Source threads: 1379860538850283571 · confidence: high_ +_Date range: 2025-06-04_ +_Reviewer note: Package: Core, symbol: null. Finding describes IDE behavior (background recompile on every keystroke). No matching reference page exists. Appropriate home would be an IDE behavior page or a 'twinBASIC IDE' / 'Diagnostics' page under docs/. Reviewer should identify or create a suitable page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If a project fails to run from the IDE (F5) but executes correctly as a compiled `.exe`, and no cause is apparent in the project code, an installed addin may be responsible. Addins are updated independently of the IDE and can become incompatible with newer betas. Restart the IDE with all addins disabled. If the project then runs correctly, re-enable addins one at a time to identify the problematic one. + +_Source threads: 1388471354663374869 · confidence: high_ +_Date range: 2025-06-28 to 2025-07-03_ +_Reviewer note: Package: VB (IDE-level workaround). No specific control or language page is a natural home. Candidate pages: a general troubleshooting page, the VB Package index, or a new IDE notes page._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> Before BETA 827, changing the **Large Address Aware (LAA)** project setting caused all open code editors to close immediately without a save prompt, discarding any unsaved work. This also occurred when the IDE displayed an out-of-memory / LAA prompt and the user followed its instruction. Fixed in BETA 827. When working with large projects that may trigger the LAA prompt, save frequently. + +_Source threads: 1388803675996295260 · confidence: high_ +_Date range: 2025-06-29 to 2025-06-30_ +_Reviewer note: Package: VB (IDE-level gotcha about the LAA project setting). No existing page covers IDE project settings. Candidate: a project settings or IDE configuration page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Before BETA 809, if a COM method was declared as a `Sub` in a Windows Dev Library (WDL) type definition but user code used its return value in an expression (treating it as a `Function`), the diagnostics engine produced no error or warning. Running the project then caused a native crash before the first line of user code executed. The workaround was to add a local `Function` re-declaration for the method, which took precedence over the WDL definition, or to call it as a `Sub` and check `Err.LastHResult` immediately after. Fixed in BETA 809. + +_Source threads: 1384893999848095834 · confidence: high_ +_Date range: 2025-06-18 to 2025-06-19_ +_Reviewer note: Package: Core (compiler diagnostics bug). No existing Core page covers WDL type definition behavior or missing diagnostics. Candidate: Core/Sub, Core/Declare, or a new compiler-diagnostics notes page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The twinBASIC IDE uses WebView2 for its interface. WebView2 is not installed by default on Windows 7 or Windows Server 2008 R2. The latest WebView2 version that supports Windows 7 is 109.0.1518.140; later versions do not support Windows 7. Users on Windows 7 must install that specific archived version manually from the Microsoft Edge archive. + +_Source threads: 1390702637929726045 · confidence: medium_ +_Date range: 2025-07-04 to 2025-07-07_ +_Reviewer note: UNMAPPED: Core package, no symbol. This is an IDE installation requirement, not a language or package API note. Suitable for a 'Getting Started' or 'System Requirements' page outside the Reference section. There is no such page currently in the docs._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Before BETA 850, the bang (`!`) shorthand for default-member access (e.g. `myRs!FieldName`) produced incorrect results in some contexts, including COM-based recordset wrappers. The confirmed workaround was to replace `myRs!FieldName` with `myRs("FieldName").Value`. The operator was fixed in BETA 850. + +_Source threads: 1392064464781709362 · confidence: high_ +_Date range: 2025-07-08 to 2025-07-09_ +_Reviewer note: UNMAPPED: package=Core, symbol=bang operator (!). No dedicated page exists for the bang/default-member-access operator. Consider adding a note to docs/Reference/Operators.md or creating a new Core page for the ! operator. The finding describes a pre-BETA-850 bug, now fixed._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Before BETA 853, twinBASIC embedded a 5-digit zero-padded revision segment in a compiled binary's string file version (e.g. `1.0.00001`) rather than the 4-digit format used by VB6 (e.g. `1.0.0001`). Code that parsed the version string expecting VB6 format received an unexpected result. Fixed in BETA 853. + +_Source threads: 1392401750450765914 · confidence: high_ +_Date range: 2025-07-09 to 2025-07-10_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This relates to project file-version embedding. Consider placing a note on a Project Configuration page or on the App class page (docs/Reference/VB/App/index.md) under the FileVersion / RevisionMajor / RevisionMinor properties._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When the IDE opens a project whose LAA (Large Address Aware) or DEP (Data Execution Prevention) settings differ from the active compiler configuration, it reloads the project using the correct compiler binary. In earlier versions this was indicated by a "Restarting from FILE" message in the debug console; this message was removed around BETA 860 to reduce confusion. The reload is normal behavior, not a crash. From around the same release, the default LAA setting for new projects changed to enabled, matching the behavior of modern Office applications. + +_Source threads: 1394472998077075456 · confidence: high_ +_Date range: 2025-07-15_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This is an IDE-level clarification with no target page. Consider adding to docs/Features/Compiler-IDE/IDE-Features.md or a dedicated debug console / project settings page._ + +--- + +## UNMAPPED · after-remarks + +When a project cannot be saved, two recovery steps are available: + +1. Use the **compiler reset** button on the toolbar, then attempt to save again. +2. If saving still fails, use **File > Export** to export the project as individual source files, then reimport those files into a fresh project. + +The Export approach recovers all source even when the normal save mechanism is completely broken. + +_Source threads: 1411180962498220082 · confidence: high_ +_Date range: 2025-08-30 to 2025-09-05_ +_Reviewer note: Package: Core. No existing page covers IDE troubleshooting / project save recovery. Consider adding this content to a new troubleshooting section in docs/Features/Compiler-IDE/IDE-Features.md or a dedicated troubleshooting page. The compiler reset tip overlaps with the finding in thread 1403430684219146300 and could be merged with it._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When `RegisterClass` or `RegisterClassEx` is called in startup code (such as `Sub Main`), the registered window class is **not** automatically unregistered when a debug session stops. Because the IDE compiler process stays alive between debug sessions, the class name remains registered. A second (or later) debug run therefore fails at `RegisterClass` with `ERROR_CLASS_ALREADY_EXISTS`. Call `UnregisterClass` in the application's cleanup path to remove the class before the next debug session begins. Compiled executables do not have this problem because each run is a fresh process. + +_Source threads: 1411722115589083267 · confidence: high_ +_Date range: 2025-08-31 to 2025-09-29_ +_Reviewer note: UNMAPPED -- package: VB, no specific symbol. This gotcha applies to any twinBASIC project that calls RegisterClass in startup code (Sub Main or similar). A suitable placement would be the Sub statement page (docs/Reference/Core/Sub.md) or a dedicated debug-mode behavior page if one exists. Reviewer should identify the best target page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Setting **Force DPI Awareness** in the twinBASIC project settings works as expected when running from the IDE but may not apply reliably to compiled executables. The reliable alternative is to set **Force DPI Awareness** to **NONE** in the project settings and instead declare DPI awareness in the application manifest XML. Every twinBASIC project already includes a default manifest (which enables the modern visual theme), so adding a `` element to that manifest is sufficient. This approach works consistently for both IDE runs and compiled executables. + +_Source threads: 1414201726369534003 · confidence: medium_ +_Date range: 2025-09-07_ +_Reviewer note: UNMAPPED -- package: VB, no specific symbol. This is a project-settings / manifest workaround with no clear single target page in the current doc set. Possible placements: a Project Settings or Manifest page under docs/Features/, or a general VB Package gotchas section. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> `WM_EXITSIZEMOVE` does not fire exclusively at the end of a resize gesture. It fires between `WM_NCLBUTTONDOWN` and the corresponding `WM_NCLBUTTONUP`, meaning that during rapid or repeated resizing it can fire multiple times while the mouse button is still held. `WM_NCLBUTTONUP` messages can be dropped in this sequence. Using `WM_EXITSIZEMOVE` alone does not guarantee that a deferred action runs only once per completed resize gesture. + +_Source threads: 1425710703863529492 · confidence: medium_ +_Date range: 2025-10-10_ +_Reviewer note: No existing docs page covers Win32 subclassing message-sequencing gotchas. This finding (symbol: null, package: VB) documents WM_EXITSIZEMOVE firing order. Possible placements: a general Win32 subclassing tips page (does not exist yet), or as an aside within a Resize/subclassing tutorial if one is added. The context is a partial workaround for the Slider/COM blocking-call freeze described in the sibling finding on WinNativeCommonCtls/Slider.md._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> If the twinBASIC IDE fails to launch, clearing the package cache folder `%appdata%\twinBASIC\Packages` resolves the problem in cases where stale cached packages prevent startup. The issue was addressed in BETA 914. + +_Source threads: 1445836598238380143 · confidence: high_ +_Date range: 2025-12-03 to 2025-12-07_ +_Reviewer note: UNMAPPED -- no troubleshooting or installation page exists in the current docs. Package: VB (IDE launch issue). Consider adding to a Getting Started or Troubleshooting page if one is created._ + +--- + +## UNMAPPED · after-remarks + +In twinBASIC BETA 909--927, opening a project while the Project Explorer was set to Object view caused a repeating ACCESS_VIOLATION crash that prevented the project from loading. The workaround was to switch the Project Explorer to File view before opening the project, then switch to Object view afterwards. Fixed in BETA 928. + +_Source threads: 1447949990579146802 · confidence: high_ +_Date range: 2025-12-09 to 2025-12-13_ +_Reviewer note: Package: Core (IDE behavior). Symbol: null. This is a version-specific bug already fixed in BETA 928. Consider whether it merits a note on docs/IDE/Project Explorer.md or can be omitted as obsolete. The page is not in the page-index._ + +--- + +## UNMAPPED · after-remarks + +twinBASIC BETA 920 and 921 silently corrupted BAS and CLS source files containing non-ASCII characters (accented or non-Latin letters) on save. The corruption did not appear immediately as compile errors but caused widespread runtime crashes on the next run. Projects without non-ASCII source characters were unaffected. Fixed in BETA 922. + +_Source threads: 1448057536614895858 · confidence: high_ +_Date range: 2025-12-09 to 2025-12-10_ +_Reviewer note: Package: Core (IDE/compiler behavior). Symbol: null. This is a fixed regression affecting only BETA 920--921. Consider whether it merits a brief note in a known-issues or release-history page, or can be omitted as obsolete. No suitable existing page was identified._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When implementing a light-weight COM event sink using manual memory pointers and function tables (rather than a real COM class), twinBASIC produces a different `IUnknown` reference-count sequence than VB6 or 32-bit VBA. The expected sequence is AddRef, QueryInterface, AddRef, Release, Release; twinBASIC omits the second AddRef, producing AddRef, QueryInterface, Release, Release. This unbalanced reference count can cause the IDE to enter trace mode and can lead to instability in Win32 builds. Projects that use this technique from VB6 --- including `cRegFree`-style late-bound event sinks --- require modification before they will work in twinBASIC. This issue was unresolved as of December 2025. + +_Source threads: 1450076755627872286 · confidence: medium_ +_Date range: 2025-12-15 to 2025-12-19_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. No existing page covers manual COM event sink implementation. Possible homes: a VB package 'Known issues' page, a twinBASIC Additions or Features page on COM interop, or a new dedicated page. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> twinBASIC IDE versions 909 through 922 contain an ANSI/UTF encoding bug. Opening a project that uses non-ASCII characters (such as letters from Norwegian, French, or other non-Latin alphabets) in identifiers or string literals in those versions causes the characters to be rewritten as garbage in the saved project files (for example, the Norwegian letter `å` is saved as `Ã¥`). The corruption is permanent --- switching to a newer IDE version does not repair the damaged files. Projects that use non-ASCII identifiers or string literals must not be opened in versions 909 through 922; restore from a backup taken before those versions were used. + +_Source threads: 1450112649248505856 · confidence: high_ +_Date range: 2025-12-15_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This is an IDE version-specific data-corruption warning with no matching reference page. Suitable placement would be a known-issues or IDE release-notes page if one exists, or a new section in docs/Features/Compiler-IDE/IDE-Features.md. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC, like VB6, treats the declaration site of each symbol as the authoritative source for its casing and auto-corrects all references in the project to match. When a referenced type library declares a symbol (such as a single-letter name like `X` or `Y`) in a particular casing, all uses of that name in user code are updated to match the type library's casing. This can cause parameter names in user-written procedures to appear in lower case even when the user originally typed them in upper case, because the type library symbol takes precedence in the global symbol table. This behavior is identical to VB6 and is not specific to twinBASIC. + +_Source threads: 1455161284336025689 · confidence: high_ +_Date range: 2025-12-29_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This documents the identifier casing / declaration-site auto-correction behavior. No dedicated reference page exists. Suitable placement might be docs/Reference/index.md (as a general language note), docs/Features/Language/index.md, or a new page under Features/Compiler-IDE. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> **EXE icon resource order:** When a custom application icon is set via the project's "Set Icon Form" setting, the twinBASIC IDE also embeds its own default icon with a resource ID that Windows Explorer searches before the custom icon. As a result, Explorer displays the twinBASIC default icon on the compiled EXE rather than the project icon. The workaround is to delete the default twinBASIC icon from the project's **Resources** folder in the Project Explorer before compiling. + +_Source threads: 1457563630513160385 · confidence: high_ +_Date range: 2026-01-05_ +_Reviewer note: UNMAPPED: no existing page covers EXE icon / resource embedding. Suitable target would be a Project Configuration page (e.g. /Features/Project-Configuration/Compiler-Options or a new page covering EXE resources and icon selection). Package: VB, topic: compiled EXE icon._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> +> **Find/Replace performance:** Performing hundreds of sequential replacements with the IDE's Find/Replace dialog causes the IDE to re-evaluate the entire source after each replacement, which produces progressive memory growth. An out-of-memory error appears after a large enough number of replacements; the IDE recovers by reloading from disk. For bulk replacements, edit the source file outside the IDE and paste or reload the modified content in one operation. + +_Source threads: 1457799141525684234 · confidence: high_ +_Date range: 2026-01-05 to 2026-01-12_ +_Reviewer note: UNMAPPED: no existing page covers Find/Replace behavior or IDE limitations. A suitable target would be the IDE Features page (/Features/Compiler-IDE/IDE-Features) under an "Editing Features" or "Known Limitations" subsection, once that page is expanded._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> +> **Export After Save:** The "Export After Save" project setting empties the entire target export folder before writing the exported files. If the export folder is set to the same directory as the project folder, or to any folder containing files that should not be deleted, all those files --- including the `.twinproj` file --- are erased on each save. The project settings UI displays a warning about this behavior. Keep the export target folder separate from the project folder and verify the path before enabling this feature. + +_Source threads: 1458236361877356785 · confidence: high_ +_Date range: 2026-01-06 to 2026-01-07_ +_Reviewer note: UNMAPPED: no existing page covers the Export After Save feature. A suitable target would be a new page under /Features/Project-Configuration/ or an expansion of the Import-Export Tool page to cover this IDE-side export setting._ + +--- + +## UNMAPPED · new-section + +When building an ActiveX Control project, using the SMALL code-generation model may produce a binary that fails self-registration after a successful build, and also fails to clean or de-register. Reverting to the BALANCED code-generation model restores correct build and registration behavior. This issue was fixed in BETA 981; projects on earlier builds should use BALANCED when building AX controls. + +_Source threads: 1487492921568792797 · confidence: high_ +_Date range: 2026-03-28 to 2026-05-13_ +_Reviewer note: Package: Core (IDE/project settings). No existing docs page covers code-generation model settings. Consider placing on a project-settings or known-issues page if one exists._ + +--- + +## UNMAPPED · new-section + +Importing a `.bas` file whose `VB_Name` attribute matches the name of a `Public Type` declared inside it raises a "duplicate definition in the current scope" error. The error clears after any edit to the UDT definition, even a trivial whitespace change. As a preventive measure, avoid giving modules and the types they contain the same name; a naming prefix such as `mdl` or `mod` on module names prevents collisions. This issue was fixed in BETA 981. + +_Source threads: 1491134625936838937 · confidence: high_ +_Date range: 2026-04-07 to 2026-05-13_ +_Reviewer note: Package: Core (IDE import behavior). No existing docs page covers module import quirks. Consider placing on a known-issues or IDE behavior page._ + +--- + +## UNMAPPED · new-section + +The twinBASIC IDE prompts to save individual module files (`.twin`, `.bas`, etc.) that have unsaved changes, but pending form changes may not trigger a save warning when the project is closed. As a precaution, press Ctrl+S before closing any project to ensure all changes are saved. + +_Source threads: 1490888222946099362 · confidence: medium_ +_Date range: 2026-04-07_ +_Reviewer note: Package: Core (IDE behavior). No existing docs page covers IDE save behavior. Low priority for documentation; would fit on an IDE usage or known-issues page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The twinBASIC IDE expects Windows-style CRLF line endings in `.twin` source files. A file with Unix-style LF-only line endings --- which can arise when source is generated or edited by cross-platform tools such as AI assistants --- causes syntax highlighting to fail for that file while other modules in the project are unaffected. The fix is to convert the affected files to CRLF before importing or re-importing them. Notepad++ displays the current line-ending style in its status bar and can convert files individually; the community tool [LinebreakRepair](https://github.com/fafalone/LinebreakRepair) automates bulk conversion. + +_Source threads: 1511085752669442088 · confidence: high_ +_Date range: 2026-06-01 to 2026-06-02_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. No existing page covers IDE source-file requirements or syntax-highlighting troubleshooting. Reviewer should determine the appropriate placement --- possibly a new IDE troubleshooting page or a note on the IDE/Editor page (docs/Reference/IDE/Editor.md or docs/IDE/Editor.md)._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> `FileOpenPicker` (the WinRT file picker) fails when the host process runs elevated (as Administrator). The async operation completes with `AsyncStatus_Error`; reading `IAsyncInfo.ErrorCode` returns HRESULT `80004005` (*Unspecified error*). This applies to both IDE and compiled EXE builds. As a fallback, use the classic Win32 `GetOpenFileNameW` API when the picker fails. + +To retrieve the error code from a failed WinRT async operation: + +```tb +With CType(Of IAsyncInfo)(AsyncOperation) + Debug.Print Hex$(.ErrorCode) +End With +``` + +This pattern applies to any WinRT async operation, not just `FileOpenPicker`. + +_Source threads: 1487268961061044338 · confidence: high_ +_Date range: 2026-04-06_ +_Reviewer note: UNMAPPED: no existing WinRT interop page. Package: WebView2 (thread context), but the finding is about general WinRT usage in twinBASIC --- consider a new WinRT interop page under Features/ or a dedicated reference page. The FileOpenPicker and CType(Of IAsyncInfo) findings from the same thread have been merged here._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> A XAML Island hosting a `ListView` with a custom `DataTemplate` that contains dynamically rendered `BitmapSource` content crashes hard in 32-bit mode during fast scrolling. No output appears in the debug console at crash time. The same code runs correctly in 64-bit mode under both the IDE and compiled EXE. Use 64-bit builds when hosting XAML Islands with dynamic image content. + +_Source threads: 1487268961061044338 · confidence: high_ +_Date range: 2026-03-28 to 2026-04-06_ +_Reviewer note: UNMAPPED: no existing XAML Island page. Package: WebView2 (thread context), but the finding concerns XAML Interop in twinBASIC. Consider a new XAML Island interop page or a note on an existing 32-bit/64-bit differences page (docs/Features/64bit.md). The root cause of the crash was not identified._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a project uses auto-increment versioning and the developer builds both 32-bit and 64-bit targets, each build is a separate operation that increments the version counter independently. The two output binaries therefore receive different version numbers. The workaround is to disable auto-increment and set the version number manually before building each target. + +_Source threads: 1065676447471128618 · confidence: medium_ +_Date range: 2023-01-19_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. No dedicated versioning or project settings page exists in the docs. Suggested placement: a Project Configuration or Project Settings page, once one is created. Could also go in Features/Project-Configuration/index.md or a new Versioning page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> `#Region` blocks can be nested to create a hierarchy of foldable sections. The string literal following `#Region` is the title displayed in the IDE when the block is collapsed. The IDE does not provide a dedicated region-list navigation pane; navigation relies on expanding and collapsing regions directly in the editor, or on the Outline panel. + +### Example + +This example uses nested regions to organize a large UserControl module. + +```tb +#Region "Public Interface" + #Region "Properties" + ' Property declarations... + #End Region + + #Region "Methods" + ' Method declarations... + #End Region +#End Region + +#Region "Private Implementation" +' Private helpers... +#End Region +``` + +_Source threads: 1068553637032366080 · confidence: high_ +_Date range: 2023-01-27 to 2023-03-17_ +_Reviewer note: UNMAPPED: package=Core, symbol=#Region. No dedicated #Region page exists. The IDE-Features page (docs/Reference path: docs/Features/Compiler-IDE/IDE-Features.md, permalink /Features/Compiler-IDE/IDE-Features) mentions it in a bullet. Suggested placement: a new Core/#Region page, or a subsection on the Topic-Preprocessor page (docs/Reference/Core/Topic-Preprocessor.md) alongside #If/#Const._ + +--- + +## UNMAPPED · new-section + +## Calling WinRT APIs from twinBASIC + +twinBASIC can call WinRT (Windows Runtime) APIs using flat-API declarations from `combase.dll`. The key steps are: + +1. Define the `IInspectable` interface with `[InterfaceId("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90")]` extending `IUnknown`. +2. Declare `RoActivateInstance` and `RoGetActivationFactory` from `combase.dll` using `DeclareWide PtrSafe`. +3. Use `WindowsCreateString` to produce an `HSTRING` from the runtime class name, then call `RoActivateInstance` or `RoGetActivationFactory` to obtain the factory or instance. +4. Call `QueryInterface` on the result to obtain the specific WinRT interface needed. + +The `VBC_Uwp*` sample projects on activevb.de demonstrate this pattern in full. + +_Source threads: 1115452453261938690 · confidence: medium_ +_Date range: 2023-06-06 to 2023-06-30_ +_Reviewer note: UNMAPPED -- no existing reference page covers WinRT interop. Consider adding this content to a Features page (e.g. docs/Features/Language/) or to a new interop guide. The pattern is community-discovered and should be verified against a working sample before publishing._ + +--- + +## UNMAPPED · new-section + +## Opening project and build-output folders from the IDE + +After a successful build, the debug console displays an **Open Folder** link next to the **Launch EXE** link; clicking it opens the build output directory in Windows Explorer. + +Starting from BETA 356, two items are also available under the **Project** menu: + +- **Open Project Folder** -- opens the directory containing the `.twinproj` file. +- **Open Build Output Folder** -- opens the compiled output directory. + +_Source threads: 1127029297601859706 · confidence: high_ +_Date range: 2023-07-08 to 2023-07-12_ +_Reviewer note: UNMAPPED -- no existing reference page covers IDE build workflow. Consider adding this to a Features or IDE guide page (e.g. docs/Documentation/ or docs/Features/). Content was confirmed by maintainer WaynePhillipsEA._ + +--- + +## UNMAPPED · after-remarks + +IDE layout menu items each expose a command name visible in the status bar when hovering over the menu item. That command name can be bound to a keyboard shortcut in the keyboard shortcut editor. The 'Fullscreen' layout visible in the IDE is a carry-over from an older full-screen mode and is not currently editable. + +_Source threads: 1201274095497650247 · confidence: high_ +_Date range: 2024-01-28_ +_Reviewer note: UNMAPPED -- IDE behavior with no corresponding reference page. Consider placing on a Features/IDE page or a keyboard-shortcuts guide page if one exists._ + +--- + +## UNMAPPED · after-remarks + +The IDE signature-help widget, shown when the cursor is inside a function call, displays the symbol title and its first documentation paragraph in collapsed form. Clicking the widget expands it to show the full help text. Starting with BETA 438, an IDE option is available to suppress the first paragraph in the collapsed view, leaving only the title visible. + +_Source threads: 1204442964269596702 · confidence: high_ +_Date range: 2024-02-06_ +_Reviewer note: UNMAPPED -- IDE behavior with no corresponding reference page. Consider placing on a Features/IDE or editor-help page if one exists in docs/Features/._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When accessing arrays at the `SAFEARRAY` level, `pvData` is a pointer whose value is the address of the first element --- it is not itself the first element. Failing to dereference `pvData` (treating it as the start address rather than a pointer to the start address) is a common source of access violations and incorrect reads when building dimension-agnostic array utilities. + +_Source threads: 1214179514104676414 · confidence: medium_ +_Date range: 2024-03-06_ +_Reviewer note: Package: VBA. Symbol: SAFEARRAY/pvData. No dedicated SAFEARRAY reference page exists in the docs. Reviewer should decide whether to add a SAFEARRAY structure entry (e.g. under VBA/HiddenModule or a new low-level page) or to fold this into a relevant HiddenModule page such as GetMem4/PutMem4._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Calling `VariantCopy` on a **Variant** that contains an array copies all array elements, not just the **Variant** header. Using the pattern `VariantCopy tmp, a : VariantCopy a, b : VariantCopy b, tmp` to swap two array-containing Variants performs three full element-level traversals. To swap two arrays without copying elements, use `CopyMemory` on `ArrPtr(...)` values to exchange the underlying array descriptor pointers. + +_Source threads: 1290634192870441010 · confidence: high_ +_Date range: 2024-10-01 to 2024-10-02_ +_Reviewer note: UNMAPPED: package VBA, symbol VariantCopy. No dedicated VariantCopy page exists in docs/Reference/VBA/. Possible placements: a new VBA/HiddenModule/VariantCopy.md page, or as a note on VBA/HiddenModule/index.md. Reviewer to decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The twinBASIC IDE project tree offers a toggle between the default file-based view and an object-based view that groups artefacts by type, similar to the VB6 Project Explorer. This feature is experimental. In the object view, renaming and deleting artefacts is not available; switch back to the file view to perform those operations. + +_Source threads: 1328735421949284352 · confidence: high_ +_Date range: 2025-01-14_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null (IDE project tree). Suggest placement on a suitable IDE reference or guide page covering the project explorer / file tree, if one exists or is added._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> A known bug in the twinBASIC IDE IntelliSense dropdown causes the TAB key to append the already-typed prefix to the completed text when used after navigating the list with the arrow keys (for example, typing `txt` then pressing TAB on `txtReleaseVersion` produces `txtReleaseVersiontxt`). Use SPACE or ENTER to confirm a completion instead of TAB until this is resolved. + +_Source threads: 1335781359318208522 · confidence: high_ +_Date range: 2025-02-03_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null (IDE IntelliSense). Suggest placement on an IDE reference or known-issues page. Also check whether this bug has been fixed in a recent twinBASIC release before publishing._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When migrating a VB6 project to twinBASIC, `.bas` (standard module) and `.cls` (class module) files can be imported individually. `.frm` (Form) and `.ctl` (UserControl) files cannot yet be imported as standalone files outside of a full VBP project migration. Independent import of Forms and UserControls is planned but not yet implemented (as of mid-2025). + +_Source threads: 1390394744634343565 · confidence: high_ +_Date range: 2025-07-03_ +_Reviewer note: UNMAPPED: no dedicated VB6 migration guide page exists in the docs. Possible placement: a new 'VB6 Migration' page under docs/Features/, or a note on docs/Features/Packages/Import-export tool.md if that page is broadened to cover VB6 source import. Package: Core, symbol: null (general migration topic). Reviewer should decide the target page before drafting._ + +--- + +## UNMAPPED · after-remarks + +The Library Symbol column in the package references list accepts custom names. Renaming the library symbol lets members of the package be qualified under a different prefix (for example, rename `WinDevLib` to `WDL` for use with `[MustBeQualified]`). Prepending the symbol name with `*` exposes the package's private classes and modules as publicly accessible. This feature applies to package references; for COM references it was already available before being extended to packages. + +_Source threads: 1425619533040975913 · confidence: high_ +_Date range: 2026-02-13_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null. No existing page covers the package references list or Library Symbol column. Possible target: docs/Reference/index.md, docs/Reference/Packages.md, or a new Features page. Reviewer should decide placement. Verify that '*' prefix behavior is documented in the IDE or .twin source._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> When restoring a project from an exported folder under a different (especially newer) version of twinBASIC, the built-in package files included in the `Packages` subfolder may conflict with the current IDE's built-in packages, making the project difficult to repair. The recommended mitigation is to add a `.gitignore` entry (or equivalent) that excludes the exported `Packages` subfolder from version control, so only user-authored code is tracked. + +_Source threads: 1428626555931721768 · confidence: medium_ +_Date range: 2026-02-13_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null. No existing page covers the File > Export workflow or version-control guidance. Possible target: a new Features/Export page or an existing project-setup guide. Reviewer should decide placement._ + +--- + +## UNMAPPED · new-section + +To set the application icon, place the desired `.ico` file inside the **ICON** subfolder under the project's Resources folder in the Project Explorer (create the subfolder if it does not exist). The icon that appears first alphabetically in that folder is used as the application icon. There is no dedicated icon picker in the project settings; the folder-naming convention is the current mechanism. + +_Source threads: 1136835898730885160 · confidence: medium_ +_Date range: 2023-08-04_ +_Reviewer note: Package: Core, symbol: null. No existing page covers project IDE settings / resource management. Consider adding this to a project-setup page under docs/Features/ or a new docs/Reference/Project-Settings.md. Reviewer should verify the ICON subfolder convention is still current._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> `Debug.Assert ` raises a runtime error when *condition* evaluates to **False**. When *condition* is **True**, the statement does nothing and execution continues normally. This differs from VBA and VB6, where a failed assert produces a soft debugger break rather than a runtime error. + +_Source threads: 1167482979245899868 · confidence: medium_ +_Date range: 2023-10-27_ +_Reviewer note: Package: VBA. No Debug or Debug.Assert page exists in the docs. The Debug object is not listed in the page index. This note should be placed on a Debug object or Debug.Assert page when one is created, or appended to an appropriate VBA module page if the Debug object lives in one._ + +--- + +## UNMAPPED · new-section + +## Importing VB6 source files + +A `.twinproj` file is self-contained: all source code is stored inside the project file, not as separate `.cls`, `.bas`, or `.frm` files on disk. When a VB6 source file is imported, its contents are copied into the `.twinproj` file; the original file on disk is not referenced or kept in sync. + +To import a VB6 `.cls` or `.bas` file: + +1. In the project explorer, right-click the **Sources** folder and choose **Import file...**. +2. Select the file. Its contents are copied into the project. + +If the imported file shows all text in plain white or grey without syntax highlighting, the file is outside the **Sources** node --- drag it into **Sources** to fix this. + +To import an entire VB6 project including forms and user controls, use the **Import from VBP** option instead of starting from **Standard EXE**. + +_Source threads: 1187620370438688850 · confidence: high_ +_Date range: 2023-12-22_ +_Reviewer note: UNMAPPED: no existing docs page covers importing VB6 source files or the self-contained .twinproj format. Likely belongs in a new IDE workflow or migration page under docs/Features/ or docs/Reference/. The 'external file references like .vbp' feature was noted as planned but not yet available as of late 2023 -- verify current status before publishing._ + +--- + +## UNMAPPED · example + +## Accessing the MS Access object model from a COM add-in + +When porting MS Access VBA code to a twinBASIC COM add-in (such as a Sample 5-style COM DLL), two changes are required: + +1. Add a reference to the **Microsoft Access N.0 Object Library** under **Project > References**, where N matches the installed Office version (12 for Access 2007, 16 for Access 2016/365). +2. Replace `Application` with `applicationObject` --- the COM add-in entry-point object reference. + +Early-bound declarations are strongly preferred over late-bound `Object` declarations, because they provide IntelliSense and catch missing method calls at design time rather than at run time. + +```tb +' Replace Application with applicationObject. +' Requires: reference to "Microsoft Access 16.0 Object Library" (or 12.0 for Access 2007). +Dim CurProject As CurrentProject +Dim dbs As CurrentData +Dim obj As AccessObject +Set CurProject = applicationObject.CurrentProject +Set dbs = applicationObject.CurrentData +For Each obj In CurProject.AllForms + If obj.IsLoaded Then + ' obj.Name is the form name + End If +Next obj +``` + +_Source threads: 1190197259862802483 · confidence: high_ +_Date range: 2023-12-29 to 2023-12-30_ +_Reviewer note: UNMAPPED: no existing docs page covers COM add-in patterns for specific Office host applications. Likely belongs in a new tutorial or guide page, possibly alongside the Sample 5 COM add-in documentation. Consider placing under docs/Tutorials/ or a new COM add-in section._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The twinBASIC debugger cannot attach to an out-of-process host. COM add-in DLLs loaded by Excel, Word, or other Office applications cannot be stepped through interactively in the twinBASIC IDE. Workarounds include adding explicit logging to the add-in code, or using a third-party error-capture library (such as vbWatchdog) to record stack traces from unhandled errors. + +_Source threads: 1195081019733327973 · confidence: high_ +_Date range: 2024-01-11 to 2024-01-12_ +_Reviewer note: UNMAPPED -- Core package, no symbol. This is a debugger limitation relevant to COM add-in development. Suitable placement would be a dedicated troubleshooting or known-limitations page, or an 'IDE' section of docs/Reference/index.md or docs/Features/. Reviewer should decide the right home._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> On Windows 7 64-bit, twinBASIC requires all available Windows Updates to be installed (including the relevant service pack). Without them, twinBASIC crashes at startup with the error: *The procedure entry point EventSetInformation could not be located in the dynamic link library ADVAPI32.dll*. Dismissing the dialog leaves the process frozen. Installing all pending Windows 7 updates resolves the issue. WebView2 must also be installed separately on Windows 7. + +_Source threads: 1191272064229843014 · confidence: high_ +_Date range: 2024-01-01 to 2024-01-03_ +_Reviewer note: UNMAPPED -- Core package, no symbol. This is a system requirements / troubleshooting note. Suitable placement would be a system requirements page or a known issues / FAQ page. No such page currently exists in the reference._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> The error *failed to load the twinBASIC DLL: bin\twinBASIC_win64.dll -- The file could not be found* can occur when twinBASIC.exe is run directly from inside a ZIP archive opened in Windows Explorer without being extracted first. Extract the entire ZIP to a regular folder before running twinBASIC.exe. If the DLL appears present on disk but the error persists, check whether antivirus software has quarantined it and add an exclusion for the installation folder. + +_Source threads: 1199424437397758043 · confidence: medium_ +_Date range: 2024-01-23 to 2024-01-24_ +_Reviewer note: UNMAPPED -- Core package, no symbol. This is a startup troubleshooting note. Suitable placement would be a known issues / FAQ or installation guide page. No such page currently exists in the reference._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC enforces stricter type compatibility for `ByRef` arguments than VB6. Passing a **Variant** variable to a `ByRef` parameter typed as a specific class or interface produces the compile-time error: *"Validation of call failed. Argument for <param>: cannot coerce type Variant to [ByRef] <Type>."* The fix is to replace the **Variant** variable with a strongly typed variable matching the parameter's declared type, or to refactor the call chain so that the correct type flows through. + +_Source threads: 1210144842559258645 · confidence: medium_ +_Date range: 2024-02-22 to 2024-02-23_ +_Reviewer note: UNMAPPED -- package: Core. No dedicated VB6 migration or type-checking page exists. Possible targets: docs/Reference/twinBASIC-Additions.md (as a type-checking note), or a new IDE/migration page. Reviewer to decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Office Ribbon getter callbacks (e.g. `getEnabled`) must be declared as a **Function** returning the value, not as a **Sub** with a `ByRef` output parameter. The Office host invokes these callbacks using `DISPATCH_PROPERTYGET`, so the Sub-with-ByRef form receives `DISP_E_EXCEPTION` and never returns a value. The correct signature is: +> +> ```tb +> Public Function OnGetEnabled(Control As IRibbonControl) As Variant +> Return True +> End Function +> ``` +> +> Additionally, the class that `Implements IRibbonExtensibility` must carry the `[WithDispatchForwarding]` attribute on that interface. Without it, the Ribbon host cannot find callbacks by name through the interface's `IDispatch`: each `Implements` interface exposes only its own members, and the attribute causes unrecognized `IDispatch` lookups to be forwarded to the outer class's implicit interface. The twinBASIC `MyCOMAddin` sample applies `[WithDispatchForwarding]` by default. +> +> The IDE's trace log shows the `Invoke` call details and is useful for diagnosing callback mismatches. + +_Source threads: 1217226824942223451 · confidence: high_ +_Date range: 2024-03-12_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. Two related findings merged -- Office Ribbon callback signature requirement and [WithDispatchForwarding] attribute requirement. No dedicated COM Addin or Office Ribbon page exists in the docs. Reviewer should decide whether to place this on a new COM Addin tutorial page, the VB Package index, or a twinBASIC Additions page._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> The Win32 `IFileDialog` COM object (e.g. `FileOpenDialog` from the WinDevLib package) fails at runtime if the project does not include a Common Controls 6.0 application manifest. In twinBASIC, this manifest is added by checking the **Visual Styles** option during project creation, or by right-clicking Resources in the Project Explorer and choosing **Add > Add Resource: Visual Styles Manifest**. The manifest entry appears under the `MANIFEST` resource in the Project Explorer. Despite the name, the primary effect of the Visual Styles manifest is to enable Common Controls 6.0, which `IFileDialog` requires. + +_Source threads: 1218956817049190420 · confidence: high_ +_Date range: 2024-03-17 to 2024-03-19_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. No dedicated IFileDialog or WinDevLib page exists. Reviewer should consider placement on a Windows API tutorial page (docs/Tutorials/Windows-API.md) or a new page for Win32 dialog patterns._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> OLE DB providers (such as `MSOLEDBSQL`, the Microsoft OLE DB Driver for SQL Server) are not Automation-compatible COM servers and cannot appear as entries in the Project References dialog. The correct approach is to reference ADO (`ADODB`) and specify the OLE DB provider by name in the connection string, for example `Provider=MSOLEDBSQL`. ADO provides the Automation-compatible layer over raw OLE DB. + +_Source threads: 1246758981805015071 · confidence: high_ +_Date range: 2024-06-02 to 2024-06-03_ +_Reviewer note: UNMAPPED: Package=Core, Symbol=null. Finding is about COM/OLE DB references in the Project References dialog. Possible targets: a FAQ or troubleshooting page, or a new note on the COM interop documentation if one exists. Reviewer should determine best placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When automating Microsoft Access from twinBASIC via COM, members such as `TempVars`, `DoCmd`, and similar Application-level objects are not implicitly available. Every such reference must be qualified with the `Application` object retrieved from the automation session (for example, `app.TempVars`). In addition, `TempVars` and related members require an open database; calling them on a freshly created `Access.Application` instance before `OpenCurrentDatabase` raises an automation error. The same pattern applies to other Office hosts: Outlook methods that depend on an open Inspector window fail if no Inspector is active. + +_Source threads: 1250769981592047737 · confidence: high_ +_Date range: 2024-06-13 to 2024-06-17_ +_Reviewer note: UNMAPPED: Package=Core, Symbol=null. Finding is about COM automation of Office applications. Possible target: VBA Interaction module (CreateObject/GetObject pages), or a dedicated COM Automation guidance page if one exists. Reviewer should determine best placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Before BETA 559, exporting a Windows Form or User Control to a `.tbform` source file produced Unix LF line endings, while all other exported source types used Windows CRLF line endings. This was corrected in BETA 559. Forms and User Controls exported before that version should be re-saved through the IDE to update their line endings. + +_Source threads: 1252249281612480632 · confidence: high_ +_Date range: 2024-06-17 to 2024-06-19_ +_Reviewer note: UNMAPPED: Package=Core, Symbol=null. Finding is about .tbform file export behaviour. Possible target: the Import/Export tool page (docs/Features/Packages/Import-export tool.md) or the Forms feature page (docs/Features/GUI-Components/Forms.md). Reviewer should determine best placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Any change to source code triggers a full recompile of the entire project. There is no incremental compilation that recompiles only the changed module. This is relevant when working in large projects where compile time is noticeable. + +_Source threads: 1252607846508396626 · confidence: high_ +_Date range: 2024-06-18_ +_Reviewer note: UNMAPPED: Package=Core, Symbol=null. Finding is about compilation behaviour (confirmed by Wayne Phillips as of BETA 559). Possible target: docs/Features/Compiler-IDE/index.md or a new compilation behaviour note. Reviewer should determine best placement and verify whether incremental compilation has been added since BETA 559._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> COM interfaces do not support method overloading. If a twinBASIC class exposes two or more methods with the same name but different parameter signatures, compilation to an ActiveX DLL or EXE fails with errors such as "failed to create typelibrary interface procedure names" and "FAILED to create type library". This restriction does not apply to twinBASIC packages (which use a different format). The fix is to give each overloaded method a distinct name, or to move the overloads into separate classes. + +_Source threads: 1295434180389372060 · confidence: medium_ +_Date range: 2024-10-14 to 2024-10-15_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. No dedicated page exists for ActiveX DLL / COM type library compilation. Consider adding this to docs/Reference/Core/CoClass.md (under a new 'COM type library restrictions' section) or to a new 'Building ActiveX components' page. The finding is about a COM/type-library constraint rather than a language statement._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> twinBASIC's debug runner executes compiled code in-process with the IDE. If the application requires Windows administrator privileges (for example, to open raw sockets or write to protected registry keys), the IDE itself must be launched with "Run as administrator". There is no project-level setting to elevate only the debug run; the elevation must be applied to the IDE process at startup. + +_Source threads: 1325528980006113280 · confidence: high_ +_Date range: 2025-01-05_ +_Reviewer note: UNMAPPED (package: Core, symbol: null). This is IDE/debugging guidance with no current home. Likely belongs on a getting-started, FAQ, or IDE debugging page. Reviewer should decide placement --- candidates include docs/Reference/index.md, a Features page, or a new IDE/debugging tips page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The license status indicator in the twinBASIC IDE stays yellow for the Community Edition (the free tier). A green indicator means a paid or upgraded license is active. A yellow indicator does not indicate a problem --- the Community Edition is fully functional for 32-bit compilation; 64-bit compiled output adds a splash screen. +> +> The service status indicators do not turn green until a project is open. Opening the IDE without creating or loading a project leaves those indicators in a non-green state. + +_Source threads: 1321545352163102802 · confidence: medium_ +_Date range: 2024-12-25_ +_Reviewer note: UNMAPPED (package: Core, symbol: null). This is IDE first-run guidance. Reviewer should place it on a getting-started, FAQ, or IDE overview page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If a compiled EXE fails with a missing-component error when run on another machine, but the same project runs without error from the IDE, two workarounds have been reported: (1) open the Project References dialog and close it without making changes, then recompile; (2) delete the affected control from the form, add a new instance of the same control with the same name, and recompile. The root cause of this reference-resolution inconsistency has not been identified. + +_Source threads: 1333848312750538886 · confidence: medium_ +_Date range: 2025-01-28_ +_Reviewer note: UNMAPPED: no existing page covers compiled-EXE deployment troubleshooting. Consider adding to the FAQ under 'Using twinBASIC', or creating a dedicated troubleshooting page. Package: Core (IDE/compiler behavior)._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The Windows **InkEdit** control is a superset of RichEdit that can display images and emoji, but it is designed for ink, touch, and pen input. Using it as a general-purpose rich-text editor causes known problems. For a RichEdit control with image-insertion support, use a dedicated `RichTextBox` implementation or a custom RichEdit wrapper that calls `EM_SETIMAGE` directly. + +_Source threads: 1334105633947127849 · confidence: medium_ +_Date range: 2025-02-15_ +_Reviewer note: UNMAPPED: package VB, symbol null. No InkEdit page exists in the docs. Reviewer should decide whether to add this as a callout on a new InkEdit stub page, on the TextBox page, or on the VB package index. Confidence is medium --- based on community report, not primary source._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Instability warnings on a twinBASIC release apply to the IDE itself (memory handling and similar issues), not to the executables it produces. Because the compiler is in Beta, any compiled executable is also considered Beta and may contain compiler-introduced bugs, but the IDE-specific warnings do not indicate that the compiled output is unsafe to distribute. + +_Source threads: 1338563655234748498 · confidence: high_ +_Date range: 2025-02-10_ +_Reviewer note: UNMAPPED: package Core, symbol null. No clear reference-doc page for this general release-quality clarification. Consider placing on docs/IDE/index.md or a dedicated FAQ / release-notes page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Some Windows type libraries (for example, UIAutomation) expose properties or methods where the underlying IDL type is `void**`, which appears as `Any` in the type library viewer. twinBASIC cannot call these members: an early-bound variable raises an error; a late-bound call crashes. The solution is to use a corrected type library --- such as oleexp or WinDevLib --- that replaces `void**` and similar opaque pointer types with `LongPtr`. This issue affects any system TLB that uses pointer types not representable in the VBx type system. + +_Source threads: 1342885188019159040 · confidence: high_ +_Date range: 2025-02-22_ +_Reviewer note: UNMAPPED: package Core, symbol null. No existing page covers type-library interop gotchas. Consider placing on a new 'Type Library Interop' page under docs/Features, or adding to docs/Reference/Core/Declare.md if a general interop-limitations section exists there._ + +--- + +## UNMAPPED · after-remarks + +When stepping into a routine that causes a native access violation or type-mismatch crash, the crash may be triggered by the IDE's LOCALS panel automatically evaluating local variables rather than by the code itself. Collapsing all items in the LOCALS panel before stepping into the suspect routine prevents the panel from auto-evaluating expressions that themselves cause the crash, allowing debugging to proceed. + +_Source threads: 1343586998073692250 · confidence: high_ +_Date range: 2025-02-24_ +_Reviewer note: Package: VB / IDE debugger. No dedicated debugging or IDE troubleshooting page exists. Reviewer should decide whether to add this to a Features/ debugging page, a new IDE Debugging page, or the tbIDE package index. The note belongs wherever IDE debugger behavior is documented._ + +--- + +## UNMAPPED · after-remarks + +Interactive step-through debugging of an ActiveX DLL called from an external host process (such as IIS/ASP or Microsoft Access) is not supported. The twinBASIC IDE requires a startup object, which ActiveX DLL projects do not have. The recommended alternative is to use `Debug.TracePrint` together with procedure entry/exit tracing to produce a verbose log that can be examined after a crash to identify the failure point. + +_Source threads: 1344957128997081159 · confidence: medium_ +_Date range: 2025-02-28 to 2025-03-05_ +_Reviewer note: Package: VB / project-type debugging. No dedicated page covers ActiveX DLL project limitations. Reviewer should decide placement: a Features/ page for DLL projects, the VB Package index, or a new debugging/troubleshooting page._ + +--- + +## UNMAPPED · after-remarks + +If the code editor panel is closed while the IDE is running and the IDE is then killed rather than shut down cleanly, the stored panel layout in the registry becomes invalid and the IDE fails to start on the next launch. The fix is to open **regedit**, navigate to `HKEY_CURRENT_USER\SOFTWARE\VB and VBA Program Settings\twinBASIC_IDE\IDESettings`, and rename or delete the `LAYOUT` value. The IDE regenerates a default layout on next start. Deleting the entire `twinBASIC_IDE` key also works but requires re-entering any purchased licence. This issue was fixed in BETA 703. + +_Source threads: 1344984160942424155 · confidence: high_ +_Date range: 2025-02-28_ +_Reviewer note: Package: IDE / installation troubleshooting. No dedicated troubleshooting page exists. Reviewer should place this in a Troubleshooting or FAQ page under docs/. The fix was reported fixed in BETA 703 so this is historical context; confirm whether it warrants documentation at all for current releases._ + +--- + +## UNMAPPED · after-remarks + +Compiled twinBASIC executables that use third-party OCX controls (such as `mswinsck.ocx`) ran correctly on machines with VB6 installed but failed with an 'Automation error' or 'instance creation failed' on machines without VB6. The root cause was a general licence embedding issue in tB's compiled output, not a missing OCX registration problem. This issue was fixed in BETA 725 (released 2025-03-28). Earlier releases have no reliable workaround short of installing VB6 on the target machine. + +_Source threads: 1345376491919380553 · confidence: high_ +_Date range: 2025-03-01 to 2025-04-09_ +_Reviewer note: Package: VB / OCX / compiled output. No dedicated page covers OCX deployment issues. Reviewer should place this in a Known Issues, Troubleshooting, or Release Notes section. Since it was fixed in BETA 725, consider whether it warrants a 'fixed in BETA 725' callout rather than a permanent note._ + +--- + +## UNMAPPED · new-section + +When a twinBASIC class implements a COM interface with overloaded methods, the vtable slot ordering for those overloaded methods differs from the ordering produced by MSVC. twinBASIC orders overloaded interface methods in the vtable in the order they appear in source; MSVC reverses them. Code that exposes overloaded interface methods via COM and is consumed by non-twinBASIC callers may see incorrect method dispatch as a result. + +_Source threads: 1357331478786080972 · confidence: medium_ +_Date range: 2025-04-03_ +_Reviewer note: UNMAPPED: package Core (or a language/COM interop page), symbol not specified. This describes a known twinBASIC compiler bug with COM interface vtable ordering for overloaded methods. Needs triage: could belong on a COM interop page, a known-issues page, or the Implements/Interface statement page. Attributed to fafalone (twinBASIC maintainer) in the source thread._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a COM type library has a different namespace qualifier in twinBASIC than in VBA (for example, the library appears as `MSInkAutLib` in VBA but as `MSINKAUTLibCtl` in twinBASIC), code that uses the qualified namespace name will not compile unchanged. The simplest fix is to use the pencil (edit) icon next to the reference in twinBASIC's **References** list and change the library name alias to match the VBA name. This avoids updating every declaration and event-handler signature in the source. + +_Source threads: 1359997097268744364 · confidence: high_ +_Date range: 2025-04-10 to 2025-04-11_ +_Reviewer note: UNMAPPED: Core, no symbol. The finding describes the References dialog's alias-edit feature. Candidate pages: a future 'COM References' page, docs/Features/Project-Configuration/index.md, or docs/Reference/twinBASIC-Additions.md. Reviewer should identify or create an appropriate page before inserting this note._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> Adding a registered ActiveX control (OCX) via the **Import from file** button in the References dialog places an invalid copy in the `ImportedTypeLibraries` folder and produces repeated error messages about missing or invalid files each time the References dialog opens. For a control that is already registered on the system, use the **Available COM References** tab and tick it there instead. The **Import from file** path is intended for type libraries that are not registered on the machine. Removing an incorrectly imported OCX from an existing project is not currently possible through the IDE; creating a new project and re-adding the reference correctly is the workaround. + +_Source threads: 1377299320885477500 · confidence: high_ +_Date range: 2025-05-28_ +_Reviewer note: UNMAPPED — no existing page covers the References dialog or COM reference management. Suitable placement would be a new Features/Project-Configuration page or a note on docs/Reference/index.md or docs/Features/Packages/index.md. Reviewer should decide where IDE-workflow gotchas of this kind belong._ + +--- + +## UNMAPPED · new-section + +The `STRRET` type in WinDevLib (WDL) requires Win64 alignment padding. Because `STRRET` is a C union whose largest member is `LPOLESTR` (8 bytes on Win64), the C compiler inserts 4 bytes of padding after the 4-byte `uType` field and 4 bytes of stride padding at the end so the total size is a multiple of 8 bytes. + +The correct definition for cross-bitness compatibility: + +```tb +Public Type STRRET + uType As ESTRRET +#If Win64 Then + lPadding As Long +#End If + cStr(0 To 259) As Byte +#If Win64 Then + lPadding2 As Long +#End If +End Type +``` + +Without `lPadding`, code that reads the `pOleStr` pointer must read from `cStr(4)` instead of `cStr(0)` on Win64. Without `lPadding2`, passing a `STRRET` to a system API that writes the full struct size can overwrite stack memory beyond the variable. + +The recommended alternative is `StrRetToBSTR` from `shlwapi.dll`, which handles all union variants correctly and requires no manual padding. + +This was corrected in WDL after the issue was identified. + +_Source threads: 1383054629574676500 · confidence: high_ +_Date range: 2025-06-13_ +_Reviewer note: UNMAPPED: this finding is about a bug in WinDevLib (WDL), a third-party library, not in twinBASIC itself. It may belong in WDL's own documentation or a twinBASIC 'gotchas / third-party' section. Package: Core (general Win32 interop). If the docs site has a WDL or interop tips page, place it there; otherwise consider omitting or linking to the WDL changelog._ + +--- + +## UNMAPPED · new-section + +> [!IMPORTANT] +> ActiveX controls that require a design-time license --- including MSComCtl2 (Microsoft Common Controls 2, `MSCOMCT2.OCX`) --- cannot be used in the twinBASIC IDE unless the design-time license is already registered in the Windows registry. VB6 registers this license automatically during installation; twinBASIC has no mechanism to do so. On a machine without VB6, the license is absent and adding the control to a project will fail. +> +> To register the license manually, locate the `MSCOMCT2.SRG` file from the VB6 installation media, prepend the line `Windows Registry Editor Version 5.00` followed by a blank line, save it with a `.reg` extension, and double-click the file to import it. Compiled applications embed the license in the executable, which is why a compiled VB6 application runs on machines that never had VB6 installed. + +_Source threads: 1384014752241680394 · confidence: high_ +_Date range: 2025-06-16_ +_Reviewer note: UNMAPPED: VB package, symbol null. This gotcha concerns third-party ActiveX design-time licensing in the twinBASIC IDE (specifically MSComCtl2 / MSCOMCT2.OCX). No existing docs page covers this. Possible placements: a new section on docs/Miscellaneous/FAQs.md, docs/Features/Project-Configuration/ActiveX-Registration.md, or a new 'Third-party controls' page. Reviewer to decide placement and adapt section heading accordingly._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> twinBASIC does not support out-of-process debugging or attaching to a running process. Breakpoints set inside a twinBASIC COM DLL project are not reached when a separate twinBASIC EXE calls into that DLL at run time. As of mid-2025 there is no "attach to process" feature in the IDE, and attaching an external debugger such as WinDbg is unreliable because twinBASIC does not produce debug symbol files. A file-based tracelog written from the DLL is the recommended workaround for runtime diagnostics in this scenario. +> +> Note that twinBASIC compiles to native code even during in-IDE execution --- unlike VB6, which ran interpreted p-code inside the IDE. This is part of what makes out-of-process debugging harder to implement, but it also means that in-IDE execution is faster than VB6 p-code. + +_Source threads: 1384557209232478240 · confidence: high_ +_Date range: 2025-06-17_ +_Reviewer note: UNMAPPED: VB package, symbol null. This covers IDE debugging limitations for cross-project COM DLL scenarios. No existing docs page covers this directly. Possible placements: a new 'Debugging' page under docs/IDE/, or an addition to docs/Miscellaneous/FAQs.md. Reviewer to decide placement._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> VB6-era ActiveX controls that require a design-time license --- such as `MSComm32.ocx` (`MSCommLib.MSComm`) --- fail to load in the twinBASIC IDE with an "instance creation failed" error unless the registry already contains the required design-time license. VB6 installs these licenses during its own setup; twinBASIC cannot install them. +> +> To use `MSComm32.ocx` in the twinBASIC IDE, install the design-time license manually. The VB6/VS6 installation CD includes a file named `MSCOMM.SRG` in the `OS\SYSTEM` folder. Add the line `Windows Registry Editor Version 5.00` at the top (followed by a blank line), save the file with a `.reg` extension, and double-click it to apply the registry entries. +> +> Executables that use the OCX at run time still require the OCX to be registered on the target machine, but do not require the design-time license. + +_Source threads: 1385884845577732157 · confidence: high_ +_Date range: 2025-06-21_ +_Reviewer note: UNMAPPED: package=VB, symbol=null (licensed ActiveX design-time registry requirement). Consider adding to a VB Package compatibility notes page or a new troubleshooting/gotchas page. Could also be merged with finding 1387517498374492261 (serial ports / MSComm) if a dedicated MSComm32 compatibility page is created._ + +--- + +## UNMAPPED · new-section + +As of mid-2025, twinBASIC does not have a native replacement for `MSComm32.ocx`. Options for serial port access are: + +- **MSComm32.ocx** --- the existing VB6 control, limited to COM ports 1 through 16. Requires the OCX to be registered on the target machine and the design-time license to be in the registry for IDE use. +- **Win32 serial port API** --- serial ports are regular file handles; `ReadFile` and `WriteFile` work on them. This approach does not scale well at high baud rates and requires a separate thread to raise events when data arrives. +- **FTDI D2XX DLL** --- available when FTDI USB-to-serial chips are in use. Allows reading USB device serial numbers that `MSComm32.ocx` cannot access. + +A native `MSComm` replacement is planned for a future twinBASIC release. + +_Source threads: 1387517498374492261 · confidence: high_ +_Date range: 2025-06-25 to 2025-07-01_ +_Reviewer note: UNMAPPED: package=VB, symbol=null (serial port / MSComm replacement guidance). Consider adding to a VB Package compatibility notes page, a new serial-port how-to, or merging with finding 1385884845577732157 (MSComm32 design-time license) on a dedicated MSComm32 / serial-port page._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> After every build, twinBASIC calls `SHChangeNotify(SHCNE_ASSOCCHANGED, ...)`, which causes Windows Explorer to flush its icon cache and redraw all desktop and taskbar icons. This is intentional: without it, Explorer continues displaying a cached old icon after the developer changes the executable's icon. The behavior can be disabled in the IDE settings if the repeated icon refresh is disruptive during iterative development. + +_Source threads: 1387833464476401685 · confidence: high_ +_Date range: 2025-06-26 to 2025-07-03_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (IDE build behavior -- desktop icon refresh). Consider adding to an IDE settings or build behavior notes page. Verify the IDE setting name that disables the icon refresh._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Importing a file into a twinBASIC project --- or deleting one --- triggers an automatic save of the `.twinproj` file. The IDE does not show a Save or Discard prompt when the project is subsequently closed. Keeping a backup copy of the `.twinproj` before importing files is a practical safeguard until this behavior is revised. + +_Source threads: 1395024930210582640 · confidence: high_ +_Date range: 2025-07-16_ +_Reviewer note: UNMAPPED --- no Reference page covers IDE file-import behavior. Suitable placements: docs/Miscellaneous/FAQs.md (under 'Using twinBASIC') or docs/IDE/Project Explorer.md. Package: Core (IDE behavior, no language symbol)._ + +--- + +## UNMAPPED · new-section + +A proof-of-concept called 'twinBASIC for Applications' was developed as an embeddable scripting runtime intended to replace VBA in host applications such as Excel. The initial work was funded by a client who subsequently chose a different direction; the feature is on hold with no committed timeline. VBScript (an alternative scripting option) has been deprecated since 2023 and is scheduled for full removal from Windows around 2027. + +_Source threads: 1407343053827608636 · confidence: high_ +_Date range: 2025-08-19 to 2025-08-20_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. This describes a roadmap/status item about an embeddable scripting runtime ('twinBASIC for Applications'). No existing page covers this topic. A reviewer should determine whether it belongs in a Features roadmap page, a FAQ, or a new section of docs/Reference/index.md or docs/Features/._ + +--- + +## UNMAPPED · new-section + +Subclassing methods that rely on assembly thunks --- a common VB6 technique --- cannot be used in twinBASIC. This is a permanent incompatibility. The recommended replacement is the Win32 `SetWindowSubclass` API. For a twinBASIC-native approach, an `ISubclass` interface pattern --- a custom COM interface with a `MessageReceived` callback, implemented on top of `SetWindowSubclass` --- works in both the IDE and compiled EXE without crashes and can be defined entirely in twinBASIC source without a separate TypeLib. + +_Source threads: 1422507930057445499 · confidence: high_ +_Date range: 2025-10-28_ +_Reviewer note: Package: VB. Symbol: null (no specific class). This finding is about a general twinBASIC-vs-VB6 incompatibility with thunk-based subclassing. A suitable target page could be a VB6-migration or twinBASIC-Additions page (e.g. docs/Reference/twinBASIC-Additions.md), or a dedicated subclassing section on the Form page. Triage placement before adding._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The floating toolbar that appears near a control when it is selected in the form editor (called the **mini bar**) can be disabled. Open **Tools > IDE Options** and uncheck the relevant option near the bottom of the list. + +_Source threads: 1421756408784752660 · confidence: high_ +_Date range: 2025-09-28_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (IDE form editor mini bar). Belongs on a page covering the twinBASIC form designer or IDE options --- no such page currently exists in the docs. Consider adding to a future 'IDE Options' or 'Form Designer' page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC has no built-in packaging or deployment wizard equivalent to the VB6 Package & Deployment Wizard. The recommended approach for distributing a twinBASIC application is a registration-free (RegFree) deployment, which requires manually identifying the required dependencies. Third-party install builders (such as Setup2Go) can also register COM components at install time. No twinBASIC-native tool currently gathers and deploys dependencies automatically. + +_Source threads: 1422329606790185111 · confidence: high_ +_Date range: 2025-09-29 to 2025-09-30_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (deployment/distribution). Belongs on a page covering deployment, packaging, or project distribution --- no such page currently exists in the docs. Consider adding to a future 'Distributing Applications' page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> A properties popup that appears automatically when a text-related control is selected in the form editor can be disabled. Open **Tools > IDE Options** and uncheck the second-to-last item in the list. + +_Source threads: 1423206371930935317 · confidence: high_ +_Date range: 2025-10-02_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (IDE form editor properties popup). Belongs on a page covering IDE Options or the form designer --- no such page currently exists. Consider merging with the mini bar IDE Options entry (thread 1421756408784752660)._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> On large 32-bit projects, the background compiler process may restart frequently due to a known memory leak. Enabling **Large Address Aware (LAA)** in the project settings reduces the frequency of these restarts by allowing the compiler process to address more than 2 GB of memory. This is a workaround, not a fix; the underlying issue remains. + +_Source threads: 1423670390592372757 · confidence: high_ +_Date range: 2025-10-03_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (compiler background process restarts). Belongs on a page covering project settings or compiler behaviour --- no such page currently exists. Consider adding to a future 'Project Settings' or 'Compiler' reference page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> twinBASIC does not support saving projects back to VB6 FRM/CTL/VBP formats, and this is not planned. The conversion is considered lossy: VB6 formats are ANSI-based while twinBASIC formats are fully Unicode-based, and twinBASIC-specific properties have no counterpart in the VB6 format. The only practical approach for maintaining a shared VB6/twinBASIC source is to keep all files in VB6 format, develop in VB6, and open the VB6-format files in twinBASIC to compile the 64-bit executable or OCX. twinBASIC supports command-line builds, which allows this step to be automated. + +_Source threads: 1425729417321119804 · confidence: high_ +_Date range: 2025-10-09_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (VB6 format export/round-trip). Belongs on a page covering VB6 compatibility or project import/export --- no such page currently exists. Consider adding to a future 'VB6 Compatibility' or 'Migrating from VB6' page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Unlike VB6, twinBASIC does not automatically renumber other controls when a control's **TabIndex** is changed in the designer. Developers must manage **TabIndex** values manually to avoid duplicates and maintain a contiguous sequence. A TabOrder addin is available from the twinBASIC community. + +_Source threads: 1425768223374049352 · confidence: high_ +_Date range: 2025-10-09_ +_Reviewer note: UNMAPPED: package=VB, symbol=null (TabIndex auto-deduplication). This is a VB form designer behaviour difference from VB6. Belongs on a page covering the form designer or the VB package overview --- no dedicated page for form designer behaviour currently exists. Could also be noted on the Form class page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a VB6 project is imported and the application icon is set to come from a form's **Icon** property, twinBASIC does not automatically copy the icon into the Resources folder. As a result, the compiled executable shows only the default Windows application icon. The workaround is to manually add the icon under **Resources > ICON**. This has been reported as a bug (GitHub issue #2244). Windows Explorer may cache the old icon after a change; restarting Explorer (`taskkill /f /im explorer.exe` then `start explorer.exe`) clears the cache. + +_Source threads: 1425834092506910851 · confidence: high_ +_Date range: 2025-10-09_ +_Reviewer note: UNMAPPED: package=Core, symbol=null (VB6 project import, icon from form). Belongs on a page covering VB6 project import or project resources --- no such page currently exists. Check whether GitHub issue #2244 has been resolved in a more recent twinBASIC release._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When the twinBASIC compiler crashes, it automatically enables Trace mode in the Debug Console. Trace-mode output in the console is a symptom of the crash, not its cause. + +> [!NOTE] +> twinBASIC does not yet support Edit and Continue. If the project is running and the source code is edited, the running program exits and the compiler may restart. This is expected behavior. Spontaneous compiler restarts during normal editing (when no program is running) indicate a separate issue and should be reported with a reproducible project. + +_Source threads: 1430397734325719131 · confidence: high_ +_Date range: 2025-10-22 to 2025-10-24_ +_Reviewer note: UNMAPPED: package=Core, no symbol. These two notes describe IDE/compiler runtime behaviors with no existing dedicated page. Candidate placements: a new 'Compiler behavior' section on docs/Features/Compiler-IDE/Debugging.md, or a new known-issues/gotchas page under Features/Compiler-IDE/. Reviewer should decide placement before drafting the page._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> If the project shows a "Bad Image" error on startup, check the project's `ImportedTypeLibraries` folder for files that are not valid type libraries or DLLs containing a type library. Plain DLLs, data files, or binaries accidentally placed in that folder trigger this error. Removing the offending files clears the error. +> +> A secondary symptom of this problem is that methods named `Item` on classes from an imported package may not appear in IntelliSense or be callable, while all other methods on the same class remain accessible. The root cause is the same invalid file in `ImportedTypeLibraries`; renaming the method to something other than `Item` works around the symptom but does not fix the underlying problem. + +_Source threads: 1431603198111780914 · confidence: high_ +_Date range: 2025-10-25 to 2025-10-26_ +_Reviewer note: UNMAPPED: package=Core, no symbol. Merges findings from threads 1431603198111780914 and 1431645468378792148. Both findings describe the same root cause (invalid files in ImportedTypeLibraries) and its two symptoms. Candidate placement: a project-setup troubleshooting section on docs/Features/Project-Configuration/index.md, or a new troubleshooting page. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> A `.twinproj` file downloaded from the internet (for example, as a Discord attachment) may carry a Windows Zone Identifier ("Mark of the Web"). This can cause twinBASIC to hang or fail when the file is opened by double-clicking or from a third-party file manager. If this happens, the IDE process may need to be ended from Task Manager. +> +> Fix: right-click the `.twinproj` file in Windows Explorer, choose **Properties**, and tick the **Unblock** checkbox before opening the file. Opening the file through the twinBASIC **File** menu instead of by shell association has also been reported to succeed in some cases. + +_Source threads: 1435151177364017222 · confidence: medium_ +_Date range: 2025-11-04 to 2025-11-05_ +_Reviewer note: UNMAPPED: package=Core, no symbol. Describes a Windows security feature interaction with the twinBASIC file association. Candidate placement: a 'Getting Started' or 'Troubleshooting' section, or alongside the ImportedTypeLibraries gotcha if a project-setup troubleshooting page is created._ + +--- + +## UNMAPPED · new-section + +## Importing VB6 forms + +Individual VB6 forms (`.frm` files) can be imported into an existing twinBASIC project without importing the whole `.vbp`. The workflow is: **Project > Import**, select the `.vbp` file that contains the form, then tick only the specific form(s) to import. + +Import is one-way --- twinBASIC cannot export a form back to the VB6 `.frm` format. Forms are fully Unicode-aware in twinBASIC, so any reverse conversion would be lossy. + +_Source threads: 1447897259403513947 · confidence: high_ +_Date range: 2025-12-09_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null (VB6 import workflow). Likely belongs on a migration/VB6 compatibility page or the IDE Import workflow page. No such page currently exists in the index; reviewer should decide whether to create a new page or fold this into an existing IDE page (e.g., docs/IDE/index.md or a new docs/IDE/Import.md)._ + +--- + +## UNMAPPED · new-section + +## Map files and debug symbols + +twinBASIC uses a custom compiler and linker and does not currently generate `.map` files or `.pdb` debug symbol files. This differs from VB6, which shared its second-stage compiler and linker with Visual C++ and could produce `.map` files. Third-party software-protection tools that require a `.map` file to embed licensing or protection code at known addresses cannot be used with twinBASIC in the same way they worked with VB6. + +twinBASIC produces native PE executables from a closed-source compiler, which provides some inherent resistance to reverse engineering without the need for a `.map` file. + +_Source threads: 1442198323053334729 · confidence: high_ +_Date range: 2025-11-23 to 2025-12-01_ +_Reviewer note: UNMAPPED -- package: Core, symbol: null (compiler output / linker capabilities). Likely belongs on a migration guide, a VB6 compatibility page, or docs/Features/Compiler-IDE/index.md. Reviewer should decide placement._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> To make all private and internal symbols from a referenced package accessible in the referencing project as if they were part of the main project, prefix the package's symbol name with an asterisk (`*`) in the project's references list. For example, entering `*MyPackage` as the symbol name exposes the package's private types --- the package still appears as `MyPackage` in the IDE. This is an undocumented feature; a dedicated project flag for the same behaviour may be added in a future release. + +_Source threads: 1456689587777704099 · confidence: high_ +_Date range: 2026-01-02_ +_Reviewer note: UNMAPPED: no existing page covers the project references list or package symbol-name configuration. Possible home: docs/IDE/Project Settings.md or a new Features/Project-Configuration page. Package: Core (project-level feature). Symbol: none (project references UI)._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> Twinpack references are not transitive. If project A references Twinpack package Foo, and Foo internally references Twinpack package Bar, Bar's types are not automatically visible in A. Each package that A needs must be added as an explicit reference in A's project settings. The syntax `Foo.Bar.BarClass` is not supported. + +_Source threads: 1463644055794614353 · confidence: high_ +_Date range: 2026-01-21 to 2026-01-22_ +_Reviewer note: Package: Core (Twinpack / references). No existing page covers Twinpack reference scoping. Possible placements: docs/Reference/index.md, a new docs/Reference/Packages.md section, or a dedicated Twinpack page under docs/Features/. Reviewer should decide the best location._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> WinRT APIs require Windows 10 or Windows 11. Code that targets machines running Windows 7 or Windows 8.x must use Win32 APIs for any OS-level functionality. + +twinBASIC does not provide a full WinRT projection. A runtime class that implements multiple interfaces requires the caller to cast to each specific interface before calling methods on it. Consulting the Windows SDK to identify which interface exposes each method is therefore necessary. The `mdlNamespaces` module is included inside the WinRT package itself and no longer needs to be added separately to a project. The `namespaces.xml` file is deliberately not part of the package --- it must be customised per project, because WinRT exposes hundreds of runtime classes and a project typically needs only a small subset. + +_Source threads: 1463785226038673552 · confidence: high_ +_Date range: 2026-01-22_ +_Reviewer note: No WinRT page exists in the docs. These findings (package: Core, symbol: null) cover WinRT platform constraints and the mdlNamespaces / namespaces.xml setup. A dedicated Features or Reference page for WinRT usage from twinBASIC would be the appropriate home. Reviewer should decide placement and expand into a full page or section._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If a project is named `SafeArray` and a function with the same name from another package is called without its package qualifier (for example, writing `SafeArray(...)` instead of `WinRT.SafeArray(...)`), an access violation occurs and the compiler enters a crash loop. This was fixed in BETA 965. + +_Source threads: 1464634730321543168 · confidence: high_ +_Date range: 2026-01-26 to 2026-01-30_ +_Reviewer note: Compiler bug fixed in BETA 965 -- no clear target page. Possible placements: a 'Known Issues' or 'Release Notes' page if one exists, the twinBASIC-Additions page, or a dedicated compiler-quirks section. Package: Core, symbol: null (compiler name-resolution collision with 'SafeArray')._ + +--- + +## UNMAPPED · new-section + +## Local package development workflow + +When iterating on a twinBASIC package without publishing to twinServ each time: + +1. In the package project settings, set the **Build output path** to the local packages folder --- for example `C:\Users\YourName\AppData\Roaming\twinBASIC\Packages\${ProjectName}.${FileExtension}`. Do not include `${architecture}` in the filename for this purpose, and note that `%APPDATA%` environment variable syntax is not expanded in this field, so the path must be written out in full. +2. In the consumer project, select the package, uncheck the **Embedded** checkbox to link it to the local file instead of the embedded copy. +3. After rebuilding the package project, click **Restart Compiler** in the consumer project to reload the updated package without closing or reopening the project. + +A `.twinproj` file placed in the linked packages folder can be referenced directly without building to a `.twinpack` first. + +To run package code interactively with a full GUI during development, temporarily change the package project type to **EXE** and assign a startup object; the IDE will run the project normally on **F5** while the output file remains a `.twinpack`. Revert the project type before publishing. + +> [!IMPORTANT] +> Conditional compilation constants defined in a package project's settings are embedded in the published `.twinpack` and remain active in any project that consumes the package. Remove or disable debug constants before publishing to avoid distributing debug-only code to consumers. + +_Source threads: 1468365586303029289 · confidence: high_ +_Date range: 2026-02-03 to 2026-02-07_ +_Reviewer note: UNMAPPED: package development workflow. No existing doc page covers the local package build/link/reload cycle. Likely belongs in a new page under docs/Documentation/ or docs/Features/, or as a section on an existing packaging guide page. Reviewer should decide placement. The conditional-compilation constant gotcha (medium confidence) is merged here._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When importing a VB6 project that includes an application manifest, the manifest's `processorArchitecture` attribute is typically set to `"x86"`. For a 64-bit twinBASIC build, change this value to `"*"` in the imported manifest resource (`#1.xml` under the Manifest section in project settings). Alternatively, add a fresh manifest via **Add resource: Visual Styles manifest** in the Add menu, though this replaces any additional settings the original VB6 manifest may contain. + +_Source threads: 1472930703384776756 · confidence: high_ +_Date range: 2026-02-16_ +_Reviewer note: UNMAPPED: Core/null -- no specific symbol. Candidate pages: a VB6 migration guide, project-settings documentation, or a new Features page about manifest resources. Reviewer should triage placement._ + +--- + +## UNMAPPED · example + +Reading a text file via the WinRT API requires the community `cAwait`/WinRT package and individual COM interface declarations rather than a full language projection. The general pattern: + +1. Call `StorageFileStatics.GetFileFromPathAsync(StrRef(path))` to get an async operation. +2. Pass the operation to `cAwait.Await()` to block until it completes. +3. Call `FileIOStatics.ReadTextAsync` with the resulting `IStorageFile`. +4. Read the result with `GetStr(.GetResults(Of LongPtr))`. + +Many WinRT runtime classes expose a *Statics* interface containing class-level methods (analogous to static methods in .NET); these do not require creating an object instance. The `cAwait`/WinRT package wraps common Statics interfaces --- including `StorageFileStatics` and `FileIOStatics` --- as public functions in a `mdlHelpers` module. + +_Source threads: 1475364753357410346 · confidence: high_ +_Date range: 2026-02-23_ +_Reviewer note: UNMAPPED: Core/null, WinRT async reading example. No existing WinRT documentation page found. Candidate placement: a new Features/WinRT page or the twinBASIC Additions page at docs/Reference/twinBASIC-Additions.md. Reviewer should triage._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> VB6 project-group files (`.vbg`) are not yet supported. When migrating a multi-project VB6 codebase, each `.vbp` file must be imported individually through **New project -- Import from VBP**. Support for `.vbg` is planned for a future release. + +_Source threads: 1486762248754958357 · confidence: high_ +_Date range: 2026-03-26 to 2026-05-31_ +_Reviewer note: Package: Core (IDE/project). Symbol: null. Best placement is docs/IDE/New Project.md (permalink /tB/IDE/Project/New) or docs/Miscellaneous/FAQs.md -- neither is in the page-index. Reviewer should add this note to the New Project page or FAQ, whichever covers VBP import workflow._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The **InitDir** property has no effect in twinBASIC. The file open dialog always opens to the folder most recently used, regardless of the value assigned to **InitDir**. This is intentional behavior and a deliberate deviation from VB6. + +Applications that run with Administrator privileges cannot use the WinRT **FileOpenPicker** class for file selection dialogs. The classic Win32 **IFileDialog** interface works correctly in elevated processes and should be used instead when Administrator mode is required. + +_Source threads: 1488446447505641584 · confidence: high_ +_Date range: 2026-04-02_ +_Reviewer note: No CommonDialog page exists in the docs (package=VB, symbol=CommonDialog). These notes belong on a CommonDialog reference page if one is created. The InitDir behavior was confirmed by fafalone as intentional. The FileOpenPicker/elevated-process restriction was confirmed by Riddler ('the WinRT one no, but the classic one works fine')._ + +--- + +## UNMAPPED · example + +When a project does not include the visual styles manifest --- for example, to preserve classic button styling --- the **CommonDialog** file open dialog renders without ComCtl6 theming. A workaround is to activate ComCtl6 only for the duration of the dialog call using Win32 activation context APIs. + +This example writes a temporary manifest file and activates it around the `ShowOpen` call, then deactivates and releases it: + +```tb +Private lCtxCookie As Long +Private hActCtx As LongPtr +Private sManifestPath As String + +Private Function ActivateCtx() As Boolean + Dim act As ACTCTX + Dim pFn As LongPtr, hLib As LongPtr + hLib = LoadLibrary("kernel32.dll") + pFn = GetProcAddress(hLib, "CreateActCtxW") + If pFn = 0 Then + Return True ' old OS will not show the new dialog anyway + End If + + ' Write a temporary manifest referencing Microsoft.Windows.Common-Controls v6 + sManifestPath = App.Path & "\temp.manifest" + + act.cbSize = LenB(act) + act.dwFlags = 0 + act.lpSource = StrPtr(sManifestPath) + hActCtx = CreateActCtx(act) + + If hActCtx = INVALID_HANDLE_VALUE Then + Return False + Exit Function + End If + + ActivateActCtx hActCtx, lCtxCookie + Return True +End Function + +Private Sub DeactivateCtx() + If hActCtx = 0 Then Exit Sub + DeactivateActCtx 0, lCtxCookie + ReleaseActCtx hActCtx + hActCtx = 0 + sManifestPath = "" +End Sub + +' Wrap the ShowOpen call: +If ActivateCtx() Then + CommonDialog1.ShowOpen + DeactivateCtx() +End If +``` + +This isolates ComCtl6 to the dialog creation so the rest of the application's controls are not affected. + +_Source threads: 1488446447505641584 · confidence: medium_ +_Date range: 2026-04-03_ +_Reviewer note: No CommonDialog page exists in the docs (package=VB, symbol=CommonDialog). This example belongs on a CommonDialog reference page if one is created. The full manifest XML was provided by fafalone as a file attachment; the reviewer should verify the exact manifest content and include it in the example (it must reference Microsoft.Windows.Common-Controls version 6). The approach works in compiled exes but not reliably in the IDE according to fafalone._ + +--- + +## UNMAPPED · new-section + +> [!IMPORTANT] +> **`.twinproj` files must be declared as binary in git.** The `.twinproj` format is a ZIP-based binary container. Git's default line-ending conversion corrupts the file when it is pushed from one machine and pulled on another, causing the project to fail to open. Add the following line to a `.gitattributes` file at the repository root: +> +> ``` +> *.twinproj binary +> ``` +> +> Without this, a project that opens correctly on the authoring machine will fail to open on any other machine that pulls the repository. + +_Source threads: 1489069674837708963 · confidence: high_ +_Date range: 2026-04-02 to 2026-05-03_ +_Reviewer note: Package: Core. No existing page covers project file management or git integration. Reviewer should decide whether to add this to a new 'Working with Git' or 'Project Configuration' page, or to an existing project setup page under docs/Features/Project-Configuration/._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> `${SourcePath}` is not a configurable project setting. It expands to the directory containing the saved `.twinproj` file, similar to `App.Path` at run time. To change what `${SourcePath}` resolves to, move or save the `.twinproj` file to the intended folder. + +_Source threads: 1506882888300363839 · confidence: medium_ +_Date range: 2026-05-21_ +_Reviewer note: UNMAPPED -- package: Core, symbol: ${SourcePath}. Suitable target would be a project-settings or build-output-path page under docs/Features/Project-Configuration/, if one is added. Alternatively, add to docs/Features/Project-Configuration/Compiler-Options.md or a new 'Build Variables' page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If the compiler reports 'build path contains unrecognized variable' but the build output path appears correct, check the project name field for a malformed placeholder. A placeholder with a mismatched closing character --- for example `${Architecture]` with a closing square bracket instead of a closing curly brace --- causes this error even when the build output path itself is valid. Correct the placeholder to use matching curly braces: `${Architecture}`. + +_Source threads: 1507014136327045281 · confidence: medium_ +_Date range: 2026-05-21_ +_Reviewer note: UNMAPPED -- package: Core, symbol: (compiler build path variable). Suitable target would be a build-variables or compiler-options reference page under docs/Features/Project-Configuration/. Consider adding alongside the ${SourcePath} note above._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When implementing a COM interface method that receives an optional `Variant` parameter backed by a pointer (for example, `IUICommandHandler.UpdateProperty`'s `currentValue` parameter in the Windows Ribbon Framework), twinBASIC will crash if the parameter holds a null pointer and code passes it to `RaiseEvent` or to `VariantCopy`. Guard against this by not forwarding the parameter when its pointer may be null --- skip the argument entirely or test before copying. + +> [!NOTE] +> A `Variant` whose type has been changed to `VT_LPWSTR` via `VariantChangeType` from a twinBASIC `String` is not safe to pass to a COM method that retains the pointer beyond the call. The underlying string buffer can be relocated or freed by the runtime, leaving the COM caller with a dangling pointer. To supply a stable `VT_LPWSTR` value, allocate a persistent buffer with `CoTaskMemAlloc((Len(s) + 1) * 2)`, copy the string data including the null terminator using `CopyMemory`, then build the `Variant` manually by setting its type field to `VT_LPWSTR` and writing the buffer pointer at offset 8: +> +> ```tb +> Dim lpBuf As LongPtr +> lpBuf = CoTaskMemAlloc((Len(s) + 1) * 2) +> CopyMemory ByVal lpBuf, ByVal StrPtr(s), (Len(s) + 1) * 2 +> Dim propvar As Variant +> VariantSetType propvar, VT_LPWSTR +> CopyMemory ByVal (VarPtr(propvar) + 8), lpBuf, LenB(Of LongPtr) +> ``` +> +> Do not assume `CoTaskMemAlloc` zeroes the returned block --- copy the null terminator explicitly. + +_Source threads: 1131299508802818150 · confidence: high_ +_Date range: 2023-07-20 to 2023-07-28_ +_Reviewer note: Package: VBA, symbol: null. These are COM interop gotchas specific to implementing COM interface callbacks in twinBASIC (observed in Windows Ribbon Framework usage). No existing reference page covers this topic. Consider placing on a COM interop guidance page if one is created, or on the Variant/Data-Types page. The VT_LPWSTR example uses VariantSetType and PointerAdd-style arithmetic --- verify the exact API names against the HiddenModule/.twin sources before publishing._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When using the VBFLXGRD16 (VB FlexGrid) package, the package entry must be placed at the top of the project's reference priority list. If it is not at the top, many errors related to the VB package occur at compile or run time. + +_Source threads: 1128939982963364001 · confidence: high_ +_Date range: 2023-07-13_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. VBFLXGRD16 is a third-party FlexGrid add-on not documented in this site. If a dedicated VBFLXGRD16 page is added, this note belongs in its Remarks section. Alternatively it could go on the VB Package index page as a compatibility note for third-party packages._ + +--- + +## UNMAPPED · example + +The undocumented `CoGetModuleType` export from `ole32.dll` returns the machine type of an executable or DLL in a single call: + +```tb +Public DeclareWide PtrSafe Function CoGetModuleType _ + Lib "ole32" ( _ + ByVal pwszFile As String, _ + pModuleType As ImageMachineType) As Long +``` + +The *pModuleType* output is a member of the `ImageMachineType` enumeration (from `tbShellLib` or equivalent). Possible values include 32-bit x86, 64-bit AMD64, 64-bit IA64, and 64-bit ARM64. + +> [!NOTE] +> `CoGetModuleType` is undocumented and may change or be removed in a future Windows version without notice. The documented alternative is to open the file, map it into memory, and manually read the `Machine` field from `IMAGE_FILE_HEADER` in the PE header. + +_Source threads: 1163387256262885376 · confidence: medium_ +_Date range: 2023-10-23_ +_Reviewer note: UNMAPPED: package=VBA, symbol=null. No existing page covers PE-header inspection or bitness detection. If a how-to page for Win32 API patterns is added, this example belongs there. Could also appear on the Declare or AddressOf page as an advanced example. The ImageMachineType type name should be verified against tbShellLib's actual declaration._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Whether a DLL can be used with **CreateObject** is not a property of the file itself --- it depends on whether a COM class it implements exposes an `IDispatch` interface. To determine this programmatically, call `LoadTypeLibEx` with `REGKIND_NONE` to load the type library without registering it, iterate the type infos looking for `TKIND_COCLASS` entries, follow `GetRefTypeOfImplType` and `GetRefTypeInfo` to retrieve `TYPEATTR`, and check `wTypeFlags` for `TYPEFLAG_FDISPATCHABLE`. The types needed are defined in `tbShellLib` (for twinBASIC / VBA 64-bit); `oleexp` provides equivalent definitions for 32-bit VBA. + +_Source threads: 1163387256262885376 · confidence: medium_ +_Date range: 2023-10-22_ +_Reviewer note: UNMAPPED: package=VBA, symbol=null. The finding is about advanced COM type-library inspection. The closest existing page is CreateObject (docs/Reference/VBA/Interaction/CreateObject.md), but the content is too advanced and tangential for that page's audience. Consider a how-to or advanced COM page. Verify tbShellLib type names before publishing._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> The Windows `IMediaRadioManager`, `IRadioInstance`, and `IRadioCollection` COM interfaces return `BSTR*` output parameters --- callee-allocated VB-style strings --- rather than the caller-allocated buffers typical for most Win32 API string outputs. Declare these parameters as `String` (or `BSTR`) by reference and do not pre-allocate a buffer; the COM object fills them directly. For example, `IRadioInstance::GetInstanceSignature(BSTR* pbstrID)` allocates its own string and writes the pointer. Passing a pre-allocated buffer corrupts the result. + +_Source threads: 1187338441541812265 · confidence: medium_ +_Date range: 2023-12-21_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. Finding describes correct declaration of BSTR* out-parameters on IMediaRadioManager/IRadioInstance COM interfaces. No existing page covers Win32 COM interop patterns at this level of detail. Reviewer should decide whether to add this to a Core interop page, a new page, or a future COM interop guide._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When `CoCreateInstance` is called with `CLSCTX_LOCAL_SERVER` to host a preview handler that cannot run in-process (for example, certain Adobe PDF or Office handlers), the handler runs in a separate process that is DPI-aware independently of the host application. If the host application is not DPI-aware, the preview rectangle coordinates are in logical (unscaled) pixels while the handler renders at physical pixels, producing a preview that appears too small on displays with scaling above 100 %. The workaround is to compare the scaling factor seen by the host against the actual system DPI, then scale the preview rectangle before passing it to the handler when a local server was used. + +_Source threads: 1196190789244555315 · confidence: medium_ +_Date range: 2024-01-15_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. Finding describes a DPI mismatch gotcha when using IPreviewHandler with CLSCTX_LOCAL_SERVER. No existing page covers IPreviewHandler or preview handler hosting. Reviewer should decide whether to place this on a new page or a future Win32 interop guide._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> VB6 project source files (`.bas`, `.cls`, `.frm`) require CRLF (`0x0D 0x0A`) line endings. When these files are stored in a Git repository with certain line-ending settings, they may be checked out with LF-only (`0x0A`) line endings. VB6 refuses to open files in that state, reporting them as corrupt. twinBASIC is tolerant and opens LF-only files successfully. Developers who need to confirm VB6 compatibility of a ported project should repair line endings in the source files before testing in VB6: replace each bare `0x0A` byte with `0x0D 0x0A`. + +_Source threads: 1183128367445319731 · confidence: medium_ +_Date range: 2023-12-09_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. Finding describes VB6-compatibility behaviour around CRLF line endings. No existing page covers file-format compatibility or VB6 project import at this level. Reviewer should consider adding this to a VB6 compatibility or migration guide page, or to the tbIDE package index under a 'VB6 project import' section._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When implementing shell drag-and-drop using the `IDropTargetHelper` COM object (also known as **DragDropHelper**), some third-party file managers --- including TotalCommander --- cause `IDropTargetHelper` initialization or image-setup to fail with an error. This does not affect file name retrieval from `IDataObject`. Wrapping the `IDropTargetHelper` calls in `On Error Resume Next` suppresses the failure and preserves the drag-and-drop file retrieval path. + +_Source threads: 1237619441525854321 · confidence: medium_ +_Date range: 2024-05-08_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. Finding is about the IDropTargetHelper / DragDropHelper COM object in shell drag-and-drop scenarios. No dedicated page exists for this COM interop topic. Reviewer should identify the best placement --- possibly the VB package index page, a Form page section on OLE drag-and-drop, or a new shell-interop guide page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> Some COM-wrapper twinpack libraries --- particularly those that P/Invoke native DLLs --- may display spurious errors in the twinBASIC IDE when the project targets 64-bit, and in some cases the 64-bit IDE process may crash during a debug run. Compiling the project to a standalone executable and running it outside the IDE works correctly for both 32-bit and 64-bit targets. Test these packages in compiled form rather than relying solely on IDE execution until IDE-level fixes are in place. + +_Source threads: 1290765435360510065 · confidence: medium_ +_Date range: 2024-10-01_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. General IDE gotcha about COM-wrapper twinpack libraries crashing the 64-bit IDE. No existing page for this topic. Reviewer should consider placement on a twinpack/packages overview page, a known-issues section, or the twinBASIC-Additions page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> If a project uses Win32 APIs that locate resources by reading the compiled executable path at run time --- for example, jump-list task icons that reference the `.exe` by path --- the project must be compiled at least once before IDE debug runs will succeed. No compiled `.exe` exists at that path until the first build, so the API call fails. Compile once to produce the binary; subsequent IDE runs will find it. + +_Source threads: 1250697686026289242 · confidence: medium_ +_Date range: 2024-06-13_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. General IDE-vs-compiled-executable gotcha about Win32 APIs that read resources from the .exe path. No existing page for this topic. Reviewer should consider placement on a project settings page, an IDE debug-run page, or a general twinBASIC gotchas section._ + +--- + +## UNMAPPED · example + +When binding to WinRT **TypedEventHandler** events, each handler for a distinct combination of sender and argument types must be declared as a separate interface with its own `InterfaceId` GUID. The interface extends **IUnknown** and exposes a single `Sub Invoke(ByVal Sender As `*SenderType*`, ByVal Args As `*ArgsType*`)`. A class that receives the event uses **Implements** on this interface. + +The event-registration method on the notifier interface (for example `addDismissed`) accepts a parameter of this typed handler interface and a **Currency** or **LongLong** token parameter passed **ByRef** to receive the registration token for later removal. + +Example for the `ToastNotification` Dismissed event: + +```tb +' Declare the event-argument interface. +[InterfaceId("...")] ' replace with the real GUID +Public Interface IToastDismissedEventArgs + Extends IInspectable + ' ... event-arg members +End Interface + +' Declare the typed handler interface --- one per event-parameter combination. +[InterfaceId("...")] ' replace with a unique GUID +Public Interface ITypedEventHandlerToastNotificationDismissed + Extends IUnknown + Sub Invoke(ByVal Sender As IToastNotification, ByVal Args As IToastDismissedEventArgs) +End Interface + +' The receiving class implements the handler interface. +Public Class ToastEventReceiver + Implements ITypedEventHandlerToastNotificationDismissed + + Private Sub ITypedEventHandlerToastNotificationDismissed_Invoke( _ + ByVal Sender As IToastNotification, _ + ByVal Args As IToastDismissedEventArgs) + ' handle dismissed event + End Sub +End Class +``` + +_Source threads: 1306091764687573062 · confidence: medium_ +_Date range: 2024-11-16_ +_Reviewer note: UNMAPPED: package=VB, symbol=null, kind=example. This is a WinRT COM interop pattern for typed event handlers with no existing documentation page. Consider adding a new page under docs/Features/Advanced/ (e.g. WinRT-Interop.md) or under docs/Reference/Core/Interface.md as a cross-reference. Verify the Invoke sub signature and InterfaceId GUID requirements against actual WinRT binding code before publishing._ + +--- + +## UNMAPPED · new-section + +## Excel XLL Add-ins + +An Excel XLL add-in is a Standard DLL project whose output file is renamed with an `.xll` extension. User-defined functions (UDFs) and event handlers are functions exported from the DLL using the `[DllExport]` attribute. The `[DllExport]` attribute on `xlAutoOpen`, `xlAutoClose`, `xlAutoRemove`, and `xlAutoAdd` hooks lets Excel call them at the appropriate lifecycle points. + +twinBASIC's **Delegate** call-by-pointer feature is used to call `Excel12`, the modern SDK entry point. The entire Excel SDK header (`xlcall.h`) has been ported to twinBASIC. + +When registering a UDF with `xlfRegister`, the `type_text` parameter must match the argument types. For example, a function taking two `XLOPER12` arguments returns the string `"UUU"`---one `U` for the return value and one per argument. + +> [!NOTE] +> +> `XLOPER12` strings (used with the `Excel12` API) support up to 32,767 characters for both input and output. The older `XLOPER` type (used with `Excel4`) is limited to 255 characters. Code ported from `Excel4`/`XLOPER` should account for this expanded limit when working with the `Excel12` API. + +> [!NOTE] +> +> When calling `Excel12v` from a twinBASIC XLL, the `opers` parameter is an array of `XLOPER12` structures---for example, `xOpers() As XLOPER12`---passed directly. Although the C prototype `LPXLOPER12 opers[]` resembles an array of pointers, the array itself is passed by reference, so passing the array variable directly is correct. Pre-populating an array of `LongPtr` values with `VarPtr` addresses is not the right approach in twinBASIC. + +> [!WARNING] +> +> When an XLL add-in is unchecked or uninstalled via Excel's Add-ins dialog, any UDFs it registered remain visible in the Function Wizard (though non-functional) until Excel is restarted. Calling `xlfUnregister` in `xlAutoClose` or `xlAutoRemove` does not fully remove the entries. The Excel SDK source itself documents this as a known bug with no complete fix. A partial workaround is to re-register each function as hidden before unregistering---this removes the functions from the Function Wizard display, but does not fully unregister them. This behavior has persisted across Excel versions since at least Excel 97. + +_Source threads: 1314442860912967680 · confidence: high_ +_Date range: 2024-12-06 to 2025-01-16_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. Merges findings from threads 1314442860912967680 (XLL hello-world) and 1318230272499777588 (XLL UDFs -- unregistration bug, XLOPER12 string limit, Excel12v array parameter). No existing docs page covers Excel XLL add-ins. Possible placements: a new Features page under docs/Features/Advanced/ (e.g. Excel-XLL-Addins.md), or a callout on docs/Features/Project-Configuration/Project-Types.md under the Standard DLLs section. The Excel12v array-parameter finding (confidence=medium) may need verification against the TBXLLUDF source before publication._ + +--- + +## UNMAPPED · after-remarks + +> [!WARNING] +> Calling the Win32 `ExitProcess` API at the end of `Sub Main` terminates the host process. When a project runs under the twinBASIC IDE debugger, this terminates the compiler process itself rather than just ending the debug run, causing the IDE to reset. Remove or conditionally skip the `ExitProcess` call when running under the IDE. + +_Source threads: 1362930208495374346 · confidence: high_ +_Date range: 2025-04-22_ +_Reviewer note: Package: VBA, symbol: null. No existing reference page covers ExitProcess or Sub Main + debugger interaction. Potential target pages: a Debugging feature page (docs/Features/Compiler-IDE/Debugging.md, not in page-index) or a new section on Sub Main caveats. Reviewer should decide placement._ + +--- + +## UNMAPPED · example + +twinBASIC can extract ZIP files using the Windows Shell32 `Folder` API without any third-party DLLs. Obtain the zip as a `Shell32.Folder` via `ShellClass.NameSpace(sZipFile)`, the destination directory as a second `Folder`, then call `Filedest.CopyHere(Filesource.Items, 20)`. The flag value `20` suppresses the progress dialog and overwrite prompts. + +```tb +Private Sub UnZip(sFileSource As String, sFileDest As String) + Dim ShellClass As Shell32.Shell + Dim Filesource As Shell32.Folder + Dim Filedest As Shell32.Folder + Set ShellClass = New Shell32.Shell + Set Filesource = ShellClass.NameSpace(sFileSource) + Set Filedest = ShellClass.NameSpace(sFileDest) + Filedest.CopyHere Filesource.Items, 20 +End Sub +``` + +To append files to an existing ZIP, use `ILCreateFromPathW` and `SHGetDesktopFolder` to obtain an `IShellFolder`, call `GetUIObjectOf` to get an `IDropTarget` on the ZIP, build a file list with `SHCreateFileDataObject`, then call `DragEnter` and `Drop` on the `IDropTarget` with `DROPEFFECT_COPY`. Both patterns work in twinBASIC 32-bit and 64-bit builds. + +_Source threads: 1344575528778334208 · confidence: high_ +_Date range: 2025-02-27_ +_Reviewer note: Package: VB, symbol: null. No existing Shell32 reference page. Possible targets: a new Shell32 / Windows Shell COM example page, or a tips/recipes section. Reviewer should decide whether this warrants a new page or is out of scope for the reference docs._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> When a project references a twinBASIC package that defines a `UUID` type (such as a WinRT package), and the project also declares its own `UUID` variables, assigning to the `.GuidString` property may raise a compiler error stating *UUID does not contain GuidString*. This happens because the compiler resolves the bare name `UUID` to the package's internal definition rather than the user's own type. The fix is to qualify the type with its declaring module or package name so the compiler resolves to the correct definition. Observed in twinBASIC build v975 when using WinRT-related COM interface declarations alongside a third-party WinRT package. + +_Source threads: 1384721515542479049 · confidence: medium_ +_Date range: 2026-03-23_ +_Reviewer note: Package=VB, no specific symbol. This is a general twinBASIC compiler name-resolution gotcha for UUID type conflicts when mixing packages that each define a UUID type. Reviewer should identify the most appropriate target page --- possibly a 'known issues' or 'COM/WinRT interop' page, or the VB Package index. No such page currently exists._ + +--- + +## UNMAPPED · new-section + +> [!NOTE] +> Interactive Windows toast notifications --- those that include pictures, text-input fields, or buttons --- require the WinRT (Windows Runtime) API. No traditional Win32 API or regular COM interface covers them. Plain notifications (non-interactive, without pictures, matching the style of Windows 7 and earlier) are achievable through older APIs such as the system tray, but anything beyond that requires WinRT. + +A note for users working from community projects: the WinRT interfaces package available on the twinBASIC package server may not match the API embedded in an older project. Function names can differ between package versions --- for example, `NewStringRef` in `mdlHSTRING.twin` was renamed to `StrRef` in a later release. When a project was built against an embedded older package and a user adds the current server-hosted package, these name differences produce compiler errors. The fix is to export the project and manually add files from the original embedded WinRT folder, or wait for the project author to update the project to the current package version. + +_Source threads: 1373417833073082405 · confidence: medium_ +_Date range: 2025-05-18 to 2025-08-22_ +_Reviewer note: Package=VB, symbol=null. No existing page covers Windows toast notifications or the WinRT package in these docs. Consider adding a page under docs/Features/ or docs/Reference/VB/ if WinRT usage is documented elsewhere, or cross-reference from the VB Package index. The WinRT package version-mismatch note is also relevant to any WinRT package reference page if one exists._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> **DesktopWindowXamlSource** (XAML Islands) requires the `` compatibility attribute in the manifest of the executable that starts the process. Without this attribute the class fails to instantiate. This applies to both twinBASIC and VB6 executables. The activation context API (used to inject manifests at runtime, for example to enable visual styles in control panel applets) does not work as a substitute --- the attribute must be present in the EXE manifest. + +> [!NOTE] +> **IDesktopWindowXamlSourceNative2** is only available on Windows 10 version 1903 (build 18362) and later. On Windows 10 1809, calling `CType(Of IDesktopWindowXamlSourceNative2)(DesktopWindowXamlSource)` raises `E_NOINTERFACE`, and a compiled executable fails silently with no window shown. The v1 interface **IDesktopWindowXamlSourceNative** works on 1809 for projects that do not require `PreTranslateMessage`. + +> [!IMPORTANT] +> XAML Islands projects using **DesktopWindowXamlSource** cannot be tested from within the twinBASIC IDE --- the XAML window does not appear when running from the IDE. The project must be compiled to an EXE and run directly. + +_Source threads: 1393553811532156949 · confidence: high_ +_Date range: 2025-07-13 to 2025-09-18_ +_Reviewer note: UNMAPPED: VB package, no existing symbol. These findings relate to WinRT XAML Islands (DesktopWindowXamlSource / IDesktopWindowXamlSourceNative2), a technique demonstrated by community member Riddler and tested by fafalone. There is no existing XAML Islands page in the docs. Reviewer should decide whether to create a new page (e.g. docs/Features/GUI-Components/ or a tutorial page) and place this content there, or note it as out of scope._ + +--- + +## UNMAPPED · example + +GPU video memory (including values above 4 GB) and the current display mode can be read from both 32-bit and 64-bit twinBASIC applications using the kernel-mode thunking functions `D3DKMTEnumAdapters` and `D3DKMTQueryAdapterInfo` from `gdi32.dll`. These are documented Win32 APIs, not driver-level calls. `KMTQAITYPE_GETSEGMENTSIZE` returns a `D3DKMT_SEGMENTSIZEINFO` structure with 64-bit `DedicatedVideoMemorySize` and `SharedSystemMemorySize` fields that work correctly in 32-bit applications, unlike the 32-bit `DedicatedVideoMemory` field in DXGI's `DXGI_ADAPTER_DESC`. The same approach also retrieves the current refresh rate via `KMTQAITYPE_CURRENTDISPLAYMODE`, which otherwise requires the heavier WMI objects. The `D3DKMT_DISPLAYMODE` structure provides width, height, format, refresh rate (as a rational number), scan-line ordering, display orientation, and flags. + +```tb +' Illustrative fragment -- full implementation at https://github.com/fafalone/GetVideoMemory +Private Sub DisplayVideoMemAndRefreshRate() + Dim segInfo As D3DKMT_SEGMENTSIZEINFO + Dim dispMode As D3DKMT_CURRENTDISPLAYMODE + Dim status As Long = D3DKMTEnumAdapters(pAdapters) + For i As Long = 0 To pAdapters.NumAdapters - 1 + If QueryAdapterInfo(pAdapters.Adapters(i).hAdapter, _ + KMTQAITYPE_GETSEGMENTSIZE, segInfo) = STATUS_SUCCESS Then + Debug.Print "Dedicated video memory: " & segInfo.DedicatedVideoMemorySize & " bytes" + If QueryAdapterInfo(pAdapters.Adapters(i).hAdapter, _ + KMTQAITYPE_CURRENTDISPLAYMODE, dispMode) = STATUS_SUCCESS Then + Debug.Print "Refresh rate: " & _ + (dispMode.DisplayMode.RefreshRate.Numerator / _ + dispMode.DisplayMode.RefreshRate.Denominator) & "Hz" + End If + End If + Next +End Sub +``` + +_Source threads: 1399820954783191171 · confidence: medium_ +_Date range: 2025-07-29 to 2025-12-02_ +_Reviewer note: UNMAPPED: VBA package, no specific symbol. This is a general system programming technique (GPU memory / display mode via gdi32.dll kernel thunks). No existing doc page covers it. Reviewer should decide whether to add a new tutorial or techniques page, or whether the WinDevLib package documentation is the more appropriate home._ + +--- + +## UNMAPPED · example + +WMI can be queried through the low-level C/C++ COM interfaces (`IWbemLocator`, `IWbemServices`, `IEnumWbemClassObject`, `IWbemClassObject`) defined in WinDevLib, rather than the VBScript-targeted scripting WMI library. After obtaining an `IWbemServices` pointer via `IWbemLocator.ConnectServer`, `CoSetProxyBlanket` must be called on the service proxy to set the authentication level to `RPC_C_AUTHN_LEVEL_CALL` and impersonation level to `RPC_C_IMP_LEVEL_IMPERSONATE`. Without this step, queries against system classes such as `Win32_Processor` fail. + +For async event monitoring (for example, `Win32_ProcessStartTrace`), use `IWbemServices.ExecNotificationQueryAsync` with a class that implements `IWbemObjectSink`. When `lObjectCount > 1` in the `Indicate` callback, additional objects beyond the first are accessed by offsetting the pointer to `apObjArray` using `vbaObjSetAddref`. + +```tb +' Illustrative fragment -- full implementation available from fafalone's WinDevLib examples +Private Function GetClockSpeedWMI() As LongLong + On Error Resume Next + Dim spLoc As IWbemLocator + Dim hr As Long = CoCreateInstance(CLSID_WbemLocator, Nothing, CLSCTX_SERVER, _ + IID_IWbemLocator, spLoc) + If (hr <> S_OK) Or (spLoc Is Nothing) Then Return 0 + + Dim spServices As IWbemServices + spLoc.ConnectServer("\\\\.\\ root\\CIMV2", vbNullString, vbNullString, _ + vbNullString, 0, vbNullString, Nothing, spServices) + If Err.LastHresult <> WBEM_S_NO_ERROR Then Return 0 + + ' Required: grant access to system-level objects. + hr = CoSetProxyBlanket(spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, _ + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, _ + 0, EOAC_NONE) + If hr <> S_OK Then Return 0 + + Dim spEnum As IEnumWbemClassObject + spServices.CreateInstanceEnum("Win32_Processor", WBEM_FLAG_SHALLOW, Nothing, spEnum) + If (Err.LastHresult <> WBEM_S_NO_ERROR) Or (spEnum Is Nothing) Then Return 0 + + Dim spInst As IWbemClassObject + Dim uCount As Long + spEnum.Next(10000, 1, spInst, uCount) + ' ... read property from spInst ... +End Function +``` + +_Source threads: 1399980896412766269 · confidence: medium_ +_Date range: 2025-07-30 to 2025-09-21_ +_Reviewer note: UNMAPPED: VBA package, no specific symbol. This technique uses WinDevLib interfaces and is not tied to any existing twinBASIC documentation page. Reviewer should decide whether to add a new tutorial or techniques page, or place this in WinDevLib-specific documentation if that exists._ + +--- + +## UNMAPPED · after-remarks + +> [!IMPORTANT] +> +> The WinRT `FileOpenPicker` class cannot be used when the process is running elevated (as administrator). Attempting to call it from an elevated IDE session or an elevated compiled EXE raises the error: 'FilePicker can't run elevated (as administrator)!'. This is a WinRT/UWP restriction, not a twinBASIC limitation. Ensure the IDE or compiled application does not run as administrator when using `FileOpenPicker`. + +_Source threads: 1424075060758708275 · confidence: high_ +_Date range: 2025-10-04_ +_Reviewer note: UNMAPPED: package=Core, symbol=null. Finding is about WinRT FileOpenPicker and elevated processes. No existing WinRT-specific page exists in the docs; reviewer should decide whether to add a WinRT usage note to docs/Features/Advanced/API-Declarations.md or create a dedicated WinRT page._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> On Windows 10 and 11, calling `IUserNotification2` to display a tray popup appears to succeed but the popup is not shown to the user. The OS converts old-style tray popups to toast notifications internally, and that conversion requires the calling application to have a Start menu shortcut (an `.lnk` file with the `System.AppUserModel.ID` property set) and a registered AppUserModelID. Without these, the notification is silently discarded. A reference implementation that creates the required Start icon, registers an AppUserModelID, and uses `IQueryContinue` correctly to avoid leaving a ghost process is available at https://github.com/fafalone/TrayUserNotification. + +_Source threads: 1477465231889399878 · confidence: high_ +_Date range: 2026-03-01 to 2026-03-03_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. This is a Windows API gotcha about IUserNotification2 on Windows 10/11, not tied to any specific VB class. Consider adding to a general Win32 interop notes page, a twinBASIC additions page, or a dedicated tutorial._ + +--- + +## UNMAPPED · after-remarks + +> [!NOTE] +> On Windows-on-ARM machines (for example, Snapdragon X Elite), calls into `opengl32.dll` --- including `wglCreateContext`, `ChoosePixelFormat`, and `SetPixelFormat` --- return failure even when OpenGL is otherwise available on the device. ARM Windows routes OpenGL through `opengl32on12.dll` rather than the standard `opengl32.dll`, and a Win32 (x86/x64) twinBASIC project compiled without explicit ARM targeting may not bind to the correct DLL. This issue was reproduced but not fully resolved; using Direct3D or another rendering API is the practical alternative on ARM hardware. + +_Source threads: 1464785863702610053 · confidence: medium_ +_Date range: 2026-01-31 to 2026-02-15_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. This is a Windows-on-ARM platform gotcha for OpenGL via opengl32.dll. Consider adding to a general platform notes page, a twinBASIC additions page, or a dedicated Win32 interop tips section._ + +--- + +## UNMAPPED · example + +### Audio playback: WinRT IMediaPlayer + +For general-purpose audio playback of compressed formats (MP3, FLAC, AAC, M4A, and others supported by Media Foundation), the WinRT `IMediaPlayer` interface requires only three steps: create a `MediaPlayer` object, set a URI source, and call `Play`. Video playback is also supported by coupling the `MediaPlayer` with a `MediaPlayerElement` control. + +```tb +Dim MediaPlayer As IMediaPlayer +Set MediaPlayer = NewObject("MediaPlayer") +MediaPlayer.SetUriSource UriFactory.CreateUri(StrRef(sFilePath)) +MediaPlayer.Play +``` + +This is substantially simpler than using `IMFPMediaPlayer` (a non-WinRT alternative that also works, using a callback interface with `Implements`) or XAudio2. XAudio2 is designed for low-latency mixing of multiple simultaneous WAV sounds --- primarily for games --- and does not natively decode compressed audio formats. + +_Source threads: 1478113080419418213 · confidence: high_ +_Date range: 2026-03-02 to 2026-03-03_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. This is an audio playback tip using WinRT IMediaPlayer. Consider adding to a tutorials page, a Win32/WinRT interop notes page, or a twinBASIC cookbook section._ + +--- + +## UNMAPPED · after-remarks + +### XInput vs. joyGetPosEx (WinMM) + +XInput and the older WinMM `joyGetPosEx` API serve complementary but distinct roles. + +XInput exposes features that `joyGetPosEx` cannot provide: + +- Left and right motor vibration independently controlled +- Left and right trigger axes as separate values --- WinMM merges both triggers into a single axis +- Thumb-stick click buttons (`XINPUT_GAMEPAD_LEFT_THUMB` / `RIGHT_THUMB`) +- Battery status +- An event queue (`XInputGetKeystroke`) with no WinMM equivalent + +`joyGetPosEx` button indices are driver-dependent: button 0 is not reliably the A button across all controllers, and the API supports no concept of player index for multiple controllers. + +`joyGetPosEx` can address non-XInput devices --- flight sticks, steering wheels, and older gamepads --- which XInput cannot reach at all. For modern XInput-compatible gamepads, XInput is the preferred API. A demo covering buttons, axes, battery, and vibration is available at https://github.com/fafalone/XInputDemo. + +_Source threads: 1484304998660837386 · confidence: medium_ +_Date range: 2026-03-19 to 2026-03-20_ +_Reviewer note: UNMAPPED: package=VB, symbol=null. This is a comparison of XInput vs. joyGetPosEx for gamepad input. Consider adding to a general Win32 input tips page or a twinBASIC cookbook section._ + +--- + +## UNMAPPED · new-section + +## WinRT SignalNotifier for non-blocking event-handle waits + +`Windows.System.Threading.Core.SignalNotifier` accepts a Win32 event handle (`HANDLE`) and fires a callback when that event is signaled, without blocking the calling thread. The class creates its own waiting thread internally, leaving the caller free to continue. + +To use `SignalNotifier` from twinBASIC, declare an interface with the IID `923C402E-4721-440E-9DDA-55B6F2E07710` that extends `IUnknown` and exposes a single `Invoke` method (commonly called `ISignalHandler`). Pass a class implementing that interface to `SignalNotifier.Attach`. + +This pattern is useful when an application has nothing to do but wait for a file-system or other Win32 event and is deployed inside a host (such as VBA in Access) that must remain responsive. It replaces `WaitForMultipleObjects` with a callback-driven approach that does not block the host's message pump. + +> [!NOTE] +> twinBASIC can declare `ISignalHandler` directly using the `[InterfaceId("923C402E-4721-440E-9DDA-55B6F2E07710")]` attribute. In plain VB6 or VBA, a type library is required to expose the WinRT interfaces, or the `ISignalHandler` vtable must be constructed manually in a `.bas` module. + +_Source threads: 1495044147449561098 · confidence: high_ +_Date range: 2026-04-21 to 2026-04-23_ +_Reviewer note: UNMAPPED -- package: VB, symbol: null. No existing page covers WinRT API patterns in twinBASIC. Candidate locations: a new page under docs/Features/Advanced/ (e.g. WinRT-Interop.md) or a section added to docs/Features/Advanced/API-Declarations.md. Reviewer should decide placement before drafting the final page._ + +--- + +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1083431012417155154, 1448280876780752936, 1417830539024662548, 1437514645354315888] + +> [!NOTE] +> +> A type library (`.tlb`) compiled for 64-bit only (for example, built with MIDL's `/env amd64` flag) will silently fail to load if the current project is configured for 32-bit mode. No architecture-mismatch error is shown, making the cause easy to misattribute. To add such a reference successfully, switch the project to 64-bit mode first, then add the TLB. If a TLB that loads correctly in a tool like OLEView still fails to load in the IDE, restarting all open twinBASIC IDE instances (not just the affected one) may clear the problem. + +_Source threads: 1160439577429954620 · confidence: medium_ +_Date range: 2023-10-08_ +_Reviewer note: Verify whether the silent-fail behavior has since been improved with a clearer error message in later IDE builds._ + +--- + +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1448280876780752936, 1417830539024662548, 1437514645354315888] + +> [!NOTE] +> The Windows Recovery Environment (WinRE) does not include the WoW64 compatibility subsystem, so 32-bit executables will not run there. A 64-bit twinBASIC build is required for utilities that must operate outside a full Windows installation. + +_Source threads: 1083431012417155154 · confidence: low_ +_Date range: 2023-03-17_ +_Reviewer note: Sourced from a single Discord post; verify that WinRE genuinely lacks WoW64 before publishing. The claim is plausible but has not been cross-checked against Microsoft documentation._ + +--- + +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1417830539024662548, 1437514645354315888] + +> [!NOTE] +> The roadmap item for running 32-bit components inside a 64-bit twinBASIC process via a proxy process covers **32-bit ActiveX controls only**. It does not extend to general-purpose 32-bit COM DLLs (such as RichClient5/6). Even for ActiveX controls, graphical operations that rely on Windows device contexts (DCs) are unlikely to work correctly when the control runs in a separate proxy process. For non-graphical 32-bit COM servers, the Windows OS-level `DLLSurrogate` mechanism may work as an alternative. + +_Source threads: 1448280876780752936 · confidence: high_ +_Date range: 2025-12-10_ +_Reviewer note: Verify the proxy-process roadmap scope against the twinBASIC roadmap or release notes before publishing; the Q3 2026 timeline and "ActiveX controls only" scope came from a Discord thread._ + +--- + +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1437514645354315888] + +> [!NOTE] +> +> When porting VB6 tooltip code that sets the `TTS_BALLOON` style via `GetWindowLong` / `SetWindowLong`, replace both calls with `GetWindowLongPtr` / `SetWindowLongPtr` for 64-bit targets. The non-`Ptr` variants silently return without applying the style in a 64-bit process, resulting in square tooltips instead of the balloon appearance. This is a standard Win32 64-bit porting requirement that applies to any window-style manipulation, not only tooltips. + +_Source threads: 1417830539024662548 · confidence: medium_ +_Date range: 2025-09-17_ +_Reviewer note: Confirm that GetWindowLong vs GetWindowLongPtr behaviour on 64-bit is consistent with the Win32 documentation; the thread reports this from practical observation._ + +--- + +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1417830539024662548] + +> [!NOTE] +> When importing resources from a VB6 `.res` file that embeds a manifest, the manifest's `processorArchitecture` attribute is typically set to `x86`. An `x86` manifest causes 64-bit builds to fail or behave incorrectly. Change `processorArchitecture` to `*` (the wildcard value) in the manifest XML so the same embedded manifest applies to both 32-bit and 64-bit builds. + +_Source threads: 1437514645354315888 · confidence: medium_ +_Date range: 2025-11-28_ + +--- + +## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1086977208171634708] + +> [!NOTE] +> **DeclareWide** is particularly useful when migrating VB6 code that aliases an ANSI entry point (e.g. `Alias "ThingA"`). Replacing `Declare` with `DeclareWide` and updating the alias to the wide entry point (e.g. `"ThingW"`) enables Unicode operation while leaving all **String** parameter types and call sites unchanged. + +_Source threads: 1303293694950244352 · confidence: medium_ +_Date range: 2024-11-06_ +_Reviewer note: The finding targets 'Core/DeclareWide', which has no standalone page in the reference index. The closest existing location is the DeclareWide section in docs/Features/Advanced/API-Declarations.md. Verify placement and consider whether a cross-reference from docs/Reference/Core/Declare.md is also needed._ + +--- + +## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1303293694950244352] + +> [!NOTE] +> From BETA 283 onwards, the IDE hover tooltip explicitly identifies whether a declaration uses **Declare** or **DeclareWide**, along with other declaration attributes such as **CDecl** and **Alias**. This distinction prevents the common mistake of treating a **DeclareWide**-declared function as ANSI and adding an unnecessary separate W-suffixed declaration. + +_Source threads: 1086977208171634708 · confidence: high_ +_Date range: 2023-03-19 to 2023-03-28_ +_Reviewer note: Insert after the DeclareWide section or as a closing note within it. Verify that BETA 283 is still the correct version boundary._ + +--- + +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1155949699585540216, 1503093611246391338, 1507659583437668512] + +## Porting VB6 Assembly Thunks + +VB6 projects often embed raw machine code as base64-encoded strings, decoding them at run time into an executable memory block and calling the resulting function pointer. This technique was used primarily to call CDecl functions or to perform low-level operations without a `Declare` statement. It relies on internal VB6 runtime implementation details that twinBASIC does not replicate, so these thunks do not work when porting such a project. + +Replace them with twinBASIC-native equivalents: + +- Use `Emit()` inside a `[Naked]` function (as shown above) to inject the same byte sequence directly into the compiled output, with no run-time decoding step. +- Use a typed [**Delegate**](../../Features/Language/Delegates) function pointer and a `CDecl Declare` to call a CDecl API cleanly without any thunk. + +See [Enhanced API Declarations](API-Declarations) for `CDecl` support on `Declare` statements and callbacks. + +_Source threads: 1111087497389441095 · confidence: medium_ +_Date range: 2023-05-25 to 2023-05-29_ + +--- + +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1503093611246391338, 1507659583437668512] + +> [!IMPORTANT] +> When executing code written into a stack-allocated byte array and called through a function pointer (for example, `CallWindowProcA` used as a trampoline), twinBASIC enforces Data Execution Prevention (DEP) in two independent places: the **project settings** and the **IDE settings**. Disabling DEP only in the project settings is not sufficient --- if the IDE setting remains enabled, the thunk fails at runtime when run inside the IDE. Both settings must be set to **off** for stack-allocated executable code to work. + +_Source threads: 1155949699585540216 · confidence: high_ +_Date range: 2023-09-25_ +_Reviewer note: Verify the exact names of both DEP settings in the current twinBASIC IDE and project settings UI, and confirm they remain separate controls._ + +--- + +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1507659583437668512] + +## API hooking and memory patching + +### Reading and writing executable memory + +`WriteProcessMemory` and `ReadProcessMemory` always have full read/write access to the current process's memory, so they can patch executable pages without first changing the page's protection flags with `VirtualProtect`. Avoiding `VirtualProtect` removes the extra cleanup step and reduces the number of API calls. + +### Trampoline stubs and antivirus detection + +When constructing a trampoline or hook stub, a relative `JMP` opcode is commonly flagged as suspicious by antivirus scanners. Replacing the relative `JMP` with a `PUSH targetAddr` / `RET` sequence achieves the same unconditional transfer and is not flagged. The opcodes differ between x86 and x64: + +```tb +#If Win64 Then + ' x64: PUSH lower32; MOV [rsp+4], upper32; RET + Emit &H68 ' PUSH imm32 (low dword) + Emit targetLow, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC7, &H44, &H24, &H04 ' MOV DWORD PTR [rsp+4], imm32 + Emit targetHigh, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC3 ' RET +#Else + ' x86: PUSH imm32; RET + Emit &H68 ' PUSH imm32 + Emit target, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC3 ' RET +#End If +``` + +_Source threads: 1503093611246391338 · confidence: medium_ +_Date range: 2026-05-11_ +_Reviewer note: The code block is illustrative; confirm exact opcode sequences against the referenced Discord project before publishing. The WriteProcessMemory/ReadProcessMemory preference over VirtualProtect is medium-confidence community advice, not a documented compiler behavior._ + +--- + +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1503093611246391338] + +> [!IMPORTANT] +> Vectored Exception Handler (VEH) techniques that modify the CPU context --- setting `Eip` (32-bit) or `Rip` (64-bit) to skip a faulting instruction --- work only in compiled executables. The same code does not function when run inside the twinBASIC IDE in interpreted (debug) mode. VEH-based safe-copy or access-violation recovery patterns cannot protect code running in the IDE. + +_Source threads: 1507659583437668512 · confidence: high_ +_Date range: 2026-05-23_ + +--- + +## docs/Features/Advanced/Multithreading.md · after-remarks + +## COM on background threads + +Background threads that need to create or call COM objects must call `OleInitialize` (or `CoInitialize` / `CoInitializeEx`) on the thread before using COM. The main UI thread initialises COM automatically; background threads created with `CreateThread` do not inherit that initialisation. + +```tb +Private Declare PtrSafe Function OleInitialize Lib "ole32" ( _ + ByVal pvReserved As LongPtr) As Long +Private Declare PtrSafe Sub OleUninitialize Lib "ole32" () + +Public Sub MyBackgroundThread() + OleInitialize 0 + ' ... use COM objects here ... + OleUninitialize +End Sub +``` + +_Source threads: 1099362561482301523 · confidence: medium_ +_Date range: 2023-05-12_ +_Reviewer note: Confidence is medium: the claim that the main UI thread initialises COM automatically should be verified against twinBASIC runtime behaviour. The OleInitialize requirement is standard Win32 practice but the automatic main-thread initialisation claim comes from a community discussion, not primary source documentation._ + +--- + +## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1437751213981700156] + +> [!NOTE] +> The twinBASIC IDE includes an automatic safe mode that detects repeated rapid compiler restarts and breaks the loop. If the compiler restarts several times in quick succession --- for example, when editing a source file that contains an `Import Library` statement triggers a compiler error --- the IDE enters safe mode automatically, allowing edits to be made before the next full restart. Safe mode can also be entered manually at startup if an `Import Library`-related edit causes persistent restart failures. + +_Source threads: 1208948615368941579 · confidence: high_ +_Date range: 2024-02-19 to 2024-02-20_ +_Reviewer note: This is based on a fixed bug from BETA 452-453 (February 2024). The auto-safe mode feature shipped in BETA 453. Verify that automatic safe mode is still present in current builds and that the description of its trigger conditions is accurate before publishing. The specific `Import Library` editing bug that caused the loop has been fixed; only the auto-safe mode note is intended here._ + +--- + +## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1208948615368941579] + +> [!IMPORTANT] +> Only C (not C++) object code can be statically linked. A `.lib` or `.obj` file that contains C++ object code --- even when the intended entry points are exposed through an `extern "C"` wrapper --- cannot be linked in twinBASIC because the linker cannot resolve C++ runtime symbols from the embedded object files. The workaround is to compile the C++ code as a DLL and use a standard `Declare` statement to access its exported functions. + +_Source threads: 1437751213981700156 · confidence: medium_ +_Date range: 2025-11-11 to 2025-11-14_ +_Reviewer note: The finding mentions SQLite Library 1.2 as an affected package due to unresolved api-ms-win-crt-math-l1-1-0 symbols. Verify whether this still applies or was resolved in a later SQLite library version. The general C-only restriction should be confirmed against the twinBASIC linker documentation or a recent build._ + +--- + +## docs/Features/Compiler-IDE/CodeLens.md · after-remarks + +> [!NOTE] +> In projects that reference large dependency packages, the **Show Run Procedure Bar** setting can cause Go to Definition to take 20--40 seconds per lookup, leaving the editor with white text during that time. The delay occurs on every lookup, even when the target file is already open. Disabling **Show Run Procedure Bar** in the project settings makes Go to Definition near-instant. This is a known performance interaction; disabling CodeLens is the current workaround when working with large packages. + +_Source threads: 1375013634656309360 · confidence: medium_ +_Date range: 2025-05-22_ +_Reviewer note: Medium confidence; verify whether this has been fixed in a recent BETA build before publishing, or add a note about which BETA version introduced the fix if one exists._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Known Limitations with Custom Win32 Message Pumps + +> [!NOTE] +> Two IDE-specific limitations apply when a program runs its own Win32 message loop (`TranslateMessage` / `DispatchMessage`) instead of using VB's built-in event model. + +**Window not displayed in the IDE.** A window created entirely via Win32 API calls --- not using a VB `Form` --- with its own message loop may fail to appear on screen when the program is launched from the twinBASIC IDE. The window handle is valid, all API calls succeed, and the window is visible in Spy++, but the window surface never renders and the `WindowProc` is never called. The same code works correctly when compiled and run as a standalone executable, and also works in VB6. The root cause has not been identified. + +**Stop and Break buttons unresponsive.** When a custom Win32 message pump is active, the IDE Stop and Break buttons do not respond. This has been observed in single-threaded programs, so it is distinct from the known limitation where the IDE loses track of execution across multiple threads. + +_Source threads: 1077672242999210076 · confidence: medium_ +_Date range: 2023-02-21_ +_Reviewer note: Findings originate from VB package / null symbol; mapped here (Debugging page) as the closest existing page for IDE-behavior gotchas. Verify whether either limitation has been fixed in a later twinBASIC release before publishing, as the thread is from February 2023. The window-not-displayed finding mentions a workaround (stepping through the message loop once with breakpoints causes the window to appear and it continues to appear after the breakpoints are cleared) -- consider whether to include it._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Immediate Memory Invalidation + +The debugger settings include an **Immediate memory invalidation** option. When enabled, freed memory is invalidated immediately rather than lazily. This causes bugs involving access to memory after it has been freed --- such as an array or object that has been released too soon --- to crash consistently rather than intermittently, making them easier to reproduce and isolate. + +> [!NOTE] +> +> This option applies only when running under the IDE debugger. It does not help diagnose crashes that occur only in a compiled binary invoked outside the IDE, such as a standard DLL called from an external host. + +_Source threads: 1099208311313813534 · confidence: high_ +_Date range: 2023-04-22_ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Native Exception &H406D1388 + +The native exception code `&H406D1388` that appears in the twinBASIC IDE output log during a debug session is a continuable C++ exception --- specifically, the standard Microsoft Visual C++ runtime "set thread name" exception. It does not indicate incorrect Basic code. + +Prior to BETA 299, the twinBASIC debugger aborted the debug session when it encountered this exception. From BETA 299 onward, the exception is caught and execution continues. The exception still appears in the IDE log, but no longer causes a session restart. No action is required. + +_Source threads: 1101704869804511265 · confidence: high_ +_Date range: 2023-05-12_ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> When trace mode is active and the **TracePrint** option is enabled in the project's trace settings, tracing can be paused and resumed at runtime using sentinel strings. Call `Debug.TracePrint "~~OFF"` to pause logging and `Debug.TracePrint "~~ON"` to resume it. Both conditions must be met: the sentinel call alone has no effect if **TracePrint** is not enabled in project settings. +> +> A common pattern is to call `Debug.TracePrint "~~OFF"` at the top of `Sub Main` to skip the trace overhead during form load, then re-enable it later --- for example, from a button click handler --- once the UI is ready. + +_Source threads: 1374402188474056704 · confidence: high_ +_Date range: 2025-05-20_ +_Reviewer note: Verify that the sentinel strings are exactly `~~OFF` and `~~ON` (case-sensitive?) and that they require the TracePrint setting to be enabled in project settings. The finding describes this clearly but the exact sentinel format and project-settings prerequisite should be confirmed against the twinBASIC source or documentation._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Recovering from a Stuck IDE + +When a code-generation error occurs during a preview or run, clicking the **Stop** button may have no effect and the IDE may become unresponsive. In this situation, use the **compiler reset** button on the toolbar to forcibly reset the compiler state. This is the general-purpose recovery action for IDE hangs caused by compiler errors. + +_Source threads: 1403430684219146300 · confidence: medium_ +_Date range: 2025-08-08 to 2025-08-14_ +_Reviewer note: Placed in the Debugging features page as a new section. If a more specific troubleshooting page exists or is created, move this there. The section heading uses new-section formatting inside after-remarks due to how the target page is structured (existing content is headings)._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> twinBASIC does not yet support **Edit and Continue**. Any code change made while execution is paused requires a full program restart before it takes effect. This is a known missing feature. + +_Source threads: 1403579629482020934 · confidence: high_ +_Date range: 2025-08-09_ +_Reviewer note: Verify that the Debugging features page does not already mention Edit and Continue. If the feature has shipped since this thread (August 2025), remove this note._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> `Debug.TracePrint` output does not appear in the Debug Console unless the trace logger destination is explicitly configured to `${DEBUG}`. Without that explicit configuration the calls are silently discarded, even though the configuration UI displays `${DEBUG}` as a hint value. This was fixed in twinBASIC BETA 951. + +_Source threads: 1454461049003835433 · confidence: high_ +_Date range: 2025-12-27 to 2026-01-16_ +_Reviewer note: Insert after the Debug Trace Logger section in Debugging.md. Confirm the BETA 951 fix version against the release log._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> +> The **Compiler services: TRACE mode** option in the IDE's About menu is unrelated to `Debug.TracePrint`. That option enables extra IDE diagnostic information written to the debug console when a crash occurs --- the IDE also turns it on automatically after a crash, for the next run. To enable `Debug.TracePrint` output, use the trace logger option in the project settings instead. + +_Source threads: 1455208656328200224 · confidence: high_ +_Date range: 2025-12-29 to 2026-01-17_ +_Reviewer note: Confirm current project settings UI label for the trace logger option before publishing._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Module-Level Variable State Between Debug Runs + +Stopping a program in the IDE and running it again without restarting the compiler does not fully reset module-level (global) variables. They can retain values from the previous run, causing unexpected behavior on subsequent debug runs. + +To guarantee a clean state, restart the compiler before each run by clicking the blue arrow next to the win32/win64 target dropdown. This reinitialises all module-level state. + +_Source threads: 1253786438454214788 · confidence: medium_ +_Date range: 2024-06-24_ +_Reviewer note: Verified from a single Discord report (medium confidence). The blue-arrow restart control description should be verified against the current IDE layout before publishing --- the UI may have changed since the thread date (2024-06-24)._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> Messages of the form "IDE internal error: Uncaught Error: ..." that appear in the twinBASIC IDE are diagnostic messages intended for the IDE developer, not errors caused by user code. Encountering one does not mean a language rule has been broken. These messages can generally be ignored when they appear alongside normal project behaviour. + +_Source threads: 1289417040540467221 · confidence: high_ +_Date range: 2024-09-28_ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +> [!NOTE] +> Memory leak warnings that appear in the twinBASIC IDE refer to memory leaks within the IDE process itself, not in compiled executables. Compiled twinBASIC programs ported from VB6 have been reported to run stably in server deployments for extended periods without the memory issues associated with the IDE. +> +> **Trace mode** is a separate concern: enabling Trace mode during a long-running session causes memory usage to climb continuously, in addition to producing large trace log files. Trace mode should not be left enabled in production or long-running deployments. + +_Source threads: 1361658401666498601 · confidence: medium_ +_Date range: 2025-04-15 to 2025-04-16_ +_Reviewer note: Confidence is medium: the claim about compiled executables running stably is based on community reports, not official documentation. Verify against any known stability issues in the twinBASIC changelog before publishing._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Diagnosing native crashes with trace mode + +When a compiled twinBASIC application crashes with a native exception (such as exception code `0xC0000005` ACCESS_VIOLATION), the Windows error report does not identify which line of Basic code triggered the crash. Enabling **trace mode** in the project settings causes the runtime to write verbose log files during execution. After reproducing the crash, examining the log file identifies the last line of code that ran before the failure. + +> [!NOTE] +> Trace mode logs are very verbose. For complex cases it may be more practical to send the log file to the twinBASIC maintainer rather than reading it directly. + +_Source threads: 1369362026119565432 · confidence: high_ +_Date range: 2025-05-06_ +_Reviewer note: Verify the exact name and location of the 'trace mode' option in project settings, and confirm whether it is exposed under Project Configuration / Compiler Options or a separate settings panel, so the prose can link to or name it precisely._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1449035570796957878, 1367648395933646929] + +## Step Into (F8) stepping over a call + +In the twinBASIC IDE debugger, pressing **F8** (Step Into) from a breakpoint may step over a function call rather than entering it. This behaviour appears when the target function contains code that the debugger cannot step through, often because of a problem in that function. + +Workaround: use **Go to Definition** on the function name to navigate to it, place a breakpoint before the problematic section, then press **F8** from that internal breakpoint to continue stepping. + +_Source threads: 1372291630773567709 · confidence: medium_ +_Date range: 2025-05-14 to 2025-05-15_ +_Reviewer note: Observed in BETA 769. Verify whether this has been fixed in a later build before publishing._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1367648395933646929] + +### Diagnosing ACCESS_VIOLATION Crashes + +When a native `ACCESS_VIOLATION` occurs during build or run and no error appears in the Project Explorer or Diagnostics pane, the trace log is the primary diagnostic tool. + +To use it: + +1. Enable the trace log in **Project Settings** before reproducing the crash. A useful starting configuration is to log procedure entry and exit only. +2. Reproduce the crash. +3. Open the resulting log and locate the thread marked with `>>>` --- this is the offending thread. +4. The thread state marker at the point of failure identifies which compiler stage crashed. For example, `GlobalVarsInitializationCodegenStart` means the crash occurred during code generation for global variable initialization, before any user code ran. + +This technique is particularly useful for crashes in generated code that produce no user-visible error message. + +_Source threads: 1449035570796957878 · confidence: high_ +_Date range: 2025-12-12_ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878] + +> [!NOTE] +> When running a project with a debugger attached, a continuable native exception with code **&H406D1388** may appear on startup. This exception is the standard Win32 thread-naming notification (documented in the Microsoft blog post *"How to: Set a Thread Name in Native Code"*) and does not affect application functionality. Resume execution to continue normally. + +_Source threads: 1367648395933646929 · confidence: medium_ +_Date range: 2025-09-12_ +_Reviewer note: The target page (docs/Features/Compiler-IDE/Debugging.md) is not listed in the page index. Confirm this is the appropriate placement, or consider the VB Package index or a general troubleshooting page. The exception is most commonly observed when the project uses WinRT classes but may appear in other contexts too._ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1463977430661464095] + +## Debug Console + +The Debug Console pane shows output from `Debug.Print` and related calls. To enter a command interactively, use the input bar --- the bar at the bottom of the pane marked with a `>` prompt. The output area above it is read-only; typing directly there has no effect. + +The input bar supports a command history: press **Up Arrow** or **Down Arrow** to cycle through previously entered commands. + +> [!NOTE] +> This is different from the VB6 Immediate window, where the same pane accepted both output and typed input. In twinBASIC, input and output use separate areas. + +_Source threads: 1077971955052982312 · confidence: high_ +_Date range: 2023-05-23_ + +--- + +## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1077971955052982312] + +## Variables Pane and Compiler Restart + +The twinBASIC debugger and compiler run in the same background process. If a property getter raises an unhandled exception while the debugger evaluates Variables pane nodes at a breakpoint, the IDE triggers a full compiler restart rather than continuing --- the shared process means memory corruption from a hard crash could affect compiler state and the internal project file system. + +The Variables pane remembers the expansion state of its nodes between breakpoints. If an expanded node contains a property getter that throws, the pane will crash each time that breakpoint is reached. + +**Workaround:** stop the debugger at a safe location first --- for example, a breakpoint in `Sub Main` or another routine that does not involve the offending object --- then collapse the root node in the Variables pane. The collapsed state is retained for the remainder of the session. + +The expansion state is stored in the project's `.meta` file in the project root folder, under the JSON key `variables_expandedCache`. The project exporter does not include this file in exports, so the cache cannot be edited from outside a running IDE session. + +> [!NOTE] +> A long-term plan exists to separate the compiler and debugger into distinct processes, which would allow the debugger to report a failed property evaluation without restarting. + +_Source threads: 1463977430661464095 · confidence: high_ +_Date range: 2026-01-23 to 2026-01-25_ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The typelib viewer resolves UDT and enum names in a referenced typelib by numeric index. If a typelib uses `importlib` to reference types from a second typelib, and the second typelib has since had new type entries inserted --- without the outer typelib being recompiled against the updated version --- the numeric indexes embedded in the outer typelib will be off by one or more. The viewer then displays the type immediately before the correct one, producing wrong UDT or enum names for those references. The fix is to recompile the outer typelib against the current version of the referenced typelib and deploy both files together as a matched set. When both typelibs are version-consistent, the viewer shows correct signatures. + +_Source threads: 1066231176462872656 · confidence: high_ +_Date range: 2023-01-21 to 2023-02-03_ +_Reviewer note: Finding targets tbIDE package (no specific symbol) but the typelib viewer is an IDE feature documented in docs/Features/Compiler-IDE/IDE-Features.md, not in the addin SDK reference. Mapped here because the existing bullet 'A type library viewer for controls and TLB files' is the natural anchor. The note should be placed after or alongside that bullet in the Advanced Features section. Verify that the version-mismatch behavior still applies to the current IDE build._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> Prior to BETA 256, the type library viewer incorrectly rendered parameters whose COM IDL declaration used `defaultvalue("")` (an empty-string default). The generated twinBASIC syntax showed a backslash-escaped quote sequence instead of a proper empty string literal, producing invalid output. This was fixed in BETA 256. + +_Source threads: 1077017629279125574 · confidence: high_ +_Date range: 2023-02-20 to 2023-02-27_ +_Reviewer note: This documents a historical bug that was fixed in BETA 256. The note is accurate but may be too version-specific for a general features page. Reviewer should decide whether to include it (as a historical gotcha for users who encountered it) or discard it as no longer relevant to current versions._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> When copying or cutting code, the IDE places spaces on the clipboard rather than literal tab characters. The number of spaces per tab matches the configured indent width. This ensures that code pasted into external editors or forums displays with consistent indentation regardless of the tab-stop settings in the destination. This behavior was introduced in BETA 288; earlier builds placed literal tab characters on the clipboard. + +_Source threads: 1091247313214783518 · confidence: high_ +_Date range: 2023-03-31 to 2023-04-04_ +_Reviewer note: Draft targets the Editing Features section of IDE-Features.md. Consider inserting this as a bullet point under that section rather than a standalone NOTE callout, depending on how the page evolves. The BETA 288 version number should be verified against the twinBASIC changelog if possible._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The form designer's **Align Left** command sets every selected control's `Left` property to the same X coordinate. Controls that were not already overlapping will overlap after the operation. This differs from Microsoft Access, where alignment preserves horizontal spacing between controls. The twinBASIC behavior matches Visual Studio WinForms. +> +> There is also no concept of a primary reference control when multiple controls are selected. In VB6, the control whose resize handles appear in a different color acts as the alignment anchor; other controls align to it. twinBASIC's designer uses a fixed rule (the minimum left coordinate) instead. Spacing adjustment commands such as **Make Spacing Equal**, **Increase Spacing**, and **Decrease Spacing** from VB6 are not yet implemented. + +_Source threads: 1122091213177229362 · confidence: high_ +_Date range: 2023-06-24_ +_Reviewer note: Verify whether spacing adjustment commands have been added to the designer since mid-2023, and whether a primary-selection concept has been implemented. The maintainer described the alignment as 'simple naïve left alignment' at that time._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> When opening a project, the editor restores the previously saved cursor position before inserting the **Run ``** codelens decorations above each procedure in standard modules. Because the decorations are inserted after position restoration, the document shifts downward and the visible area ends up roughly 10 lines above the actual last editing position. Scrolling down one page returns to the correct location. This limitation is most noticeable in large projects where codelens decorations are numerous. + +_Source threads: 1201200584821313586 · confidence: high_ +_Date range: 2024-01-28_ +_Reviewer note: Confirmed by WaynePhillipsEA in the thread as a known limitation. Verify whether this has since been fixed before publishing._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **Rename symbol** (F2 or right-click > Rename) displays a text-entry field pre-filled with the symbol name, but pressing Enter or Esc has no effect. The feature is not yet implemented; it is listed on the roadmap for 2026-Q1. As a workaround, use Find/Replace or the Monaco editor's built-in "select all occurrences" command to rename a symbol across the current file. + +_Source threads: 1216245949278457856 · confidence: high_ +_Date range: 2024-03-10 to 2025-07-15_ +_Reviewer note: Insert under the Editing Features section of IDE-Features.md. Verify the 2026-Q1 roadmap entry is still current as of the publication date._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> In the form designer, lasso-dragging *inside* a **Frame** or other container selects the container itself rather than the controls within it. To select controls inside a container, draw the lasso from *outside* the container so the selection rectangle encompasses the container and its child controls, then hold **Ctrl** and click the container to deselect it. The remaining selection covers only the inner controls. + +_Source threads: 1407004587570892974 · confidence: medium_ +_Date range: 2025-08-18_ +_Reviewer note: Placed in the Form Designer Enhancements section of the IDE Features page. If a dedicated form-designer page is created, move this note there._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The twinBASIC IDE process is 32-bit regardless of the target architecture selected for the project. When the project targets Win64, only the backend compiler process and the debugger process run as 64-bit; the IDE host process remains 32-bit. Because WebView2's memory usage grows over long editing sessions and is not fully released between compile cycles, the IDE can exhaust its 32-bit address space and crash with an out-of-memory error even on machines with large amounts of physical RAM. Save frequently during long sessions. The toolbar's **Restart Compiler** button can sometimes recover without a full IDE restart. + +_Source threads: 1453807327982649424 · confidence: high_ +_Date range: 2025-12-25 to 2025-12-30_ +_Reviewer note: This is an IDE architecture clarification. It should sit near the top of the IDE Features page or in a dedicated 'Known limitations' section. Reviewer should decide exact placement._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +## Keyboard Shortcuts + +- **Alt+Up Arrow / Alt+Down Arrow** moves the current line or selection up or down in the file. This shortcut is not listed in any menu but works in the code editor. + +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-02-22_ +_Reviewer note: Verify that this shortcut is still present and works as described in a recent build. It was discovered accidentally by a community member and confirmed via reactions, not official documentation._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> Find/replace can be restricted to a selected range of lines. Open **Edit > Replace**, enter the search and replacement terms, select the target lines in the editor, then click the rows icon in the Replace dialog (between the down-arrow button and the close button). The dialog shows how many items fall within the selection. Click **Replace All** to apply only within that range. + +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-04-22_ +_Reviewer note: Verify the exact UI layout (rows icon position) against a current build. The description is from a community member explaining the feature to another user._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **Type library import vs. reference.** twinBASIC supports importing `.tlb` and `.olb` type library files directly into a project as well as referencing them. Once a type library is present in the project, the IDE can display its definitions as equivalent twinBASIC source. This applies to third-party type libraries such as `oleexp.tlb`, `OLEGuids.tlb`, and DirectX TLBs. + +_Source threads: 1077971955052982312 · confidence: high_ +_Date range: 2023-05-29_ +_Reviewer note: The IDE-Features page already notes 'A type library viewer for controls and TLB files that displays the full contents in twinBASIC-style syntax.' Verify whether the import-vs-reference distinction is accurate and worth calling out separately, or whether this note should be merged into the existing bullet._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **IntelliSense and Enter key.** In some contexts, pressing Enter once dismisses the IntelliSense suggestion list without performing the associated expansion. A second Enter is then required to complete the action --- for example, to expand `Sub Test` into `Sub Test() ... End Sub` with the cursor inside, or to advance to the next line after completing a declaration. This differs from VB6, where a single Enter both dismissed IntelliSense and applied the selection. + +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-03-07_ +_Reviewer note: Reported by one community member without contradiction. Verify whether this is still the behavior in current builds, or whether it has been fixed since March 2023._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **Auto-insertion of block-closing keywords.** The editor inserts block-closing keywords (such as `End If`, `End Sub`) automatically when you start a new block and press Enter. This feature is less reliable in larger projects and when the line immediately after the cursor is not empty --- in those cases the editor may only indent to match the previous line instead of inserting the closing keyword. + +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-03-05 to 2023-03-06_ +_Reviewer note: Described as a known beta-era bug by fafalone. Verify whether this has been fixed in more recent builds before including._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The twinBASIC IDE is built on the Monaco editor (the same editor that powers VS Code), which does not implement overtype mode. Pressing the **Insert** key to toggle between insert and overtype modes---as expected in the classic VB6 IDE---has no effect. This is a Monaco upstream limitation, not a twinBASIC-specific decision. + +_Source threads: 1087939028826984480 · confidence: high_ +_Date range: 2023-03-22 to 2023-03-28_ +_Reviewer note: Check that the Editing Features section is the right placement on this page; insert after the existing bullet list or as a new sub-note under the overtype bullet if one is added._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **Auto-insert of `End Sub` / `End Function` / `End Property`.** The editor inserts the matching closing statement automatically when the user types a procedure header and presses Enter. This trigger activates only when the procedure keyword begins with a capital letter (for example `Sub`, `Private Sub`, `Public Function`). Typing an all-lowercase keyword such as `private sub test()` and pressing Enter does not insert the closing statement, leaving subsequent code flagged as errors. To recover, press the up-arrow key to return to the header line, move to the end of the line, and press Enter again after the IDE has capitalised the keyword. + +_Source threads: 1169977003705909349 · confidence: high_ +_Date range: 2023-11-03_ +_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list. Verify that the all-lowercase behaviour has not changed in recent IDE builds._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> **Block comment / uncomment.** Selecting multiple lines and pressing **Ctrl+/** toggles line comments on and off for the selection. This shortcut does not appear in the right-click context menu or the Edit menu; users who look for it there will not find it. + +_Source threads: 1176838610327187517 · confidence: high_ +_Date range: 2023-11-22_ +_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list (or after the auto-insert note added from thread 1169977003705909349 if that addition lands nearby)._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +## Title Bar and Taskbar Behavior + +By default the twinBASIC IDE uses a custom title bar. This custom title bar does not respond to a double-click to toggle between the maximized and restored window state, and may prevent an auto-hidden Windows taskbar from appearing over the IDE window. + +Enabling **Use real OS titlebar** in IDE Options switches to the standard Windows title bar, which restores double-click maximize/restore behavior and resolves the auto-hidden taskbar issue. + +> [!NOTE] +> A fix for the double-click maximize/restore behavior was included in BETA 874. The **Use real OS titlebar** option remains available as an alternative if the default behavior is still not preferred. + +_Source threads: 1397666706197057627 · confidence: high_ +_Date range: 2025-07-23 to 2025-09-30_ +_Reviewer note: No dedicated IDE options page exists in the docs; IDE-Features.md is the closest match. The reviewer should confirm the exact menu path for 'Use real OS titlebar' in the current IDE, and verify whether BETA 874 fully resolved the double-click behavior._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The twinBASIC IDE does not include a Procedure View / Full Module View toggle equivalent to the one found in the VB6 IDE. Code folding (expand/collapse regions and procedures) is available because it is provided by the Monaco editor. A Procedure View feature is tracked as GitHub issue #2160. + +_Source threads: 1411082650902335678 · confidence: high_ +_Date range: 2025-08-29_ +_Reviewer note: No dedicated VB6-compatibility IDE page exists. IDE-Features.md is the best available placement. Reviewer should verify that GitHub issue #2160 still accurately reflects the current status of this feature request._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +### Customising syntax colours + +The IDE loads syntax-highlight colours from two CSS files in its installation folder: + +- `styles-twinbasic-light.css` --- colours used in the Light and Classic themes. +- `styles-twinbasic-dark.css` --- colours used in the Dark theme. + +Editing these files changes the colour applied to the corresponding token classes: `.basicConstant`, `.basicVariable`, `.basicField`, `.basicEnum`, `.basicEnumMember`, `.basicKeyword`, and others. The changes take effect when the IDE reloads the stylesheet. This provides a way to adjust individual token colours without replacing an entire built-in theme. + +_Source threads: 1233485738516156486 · confidence: medium_ +_Date range: 2024-04-26 to 2024-04-27_ +_Reviewer note: Verify that `styles-twinbasic-light.css` and `styles-twinbasic-dark.css` are still the correct file names and that the CSS class names listed (`.basicConstant` etc.) are current. This mechanism may have been superseded by a built-in theme picker in later BETAs._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The IDE process has a known memory leak. Leaving the IDE running continuously for an extended period eventually exhausts available memory and produces an **Out of memory** crash. Closing the IDE after use releases all memory back to the system. Compiled twinBASIC applications are not affected by the same leak and can run continuously as background services, though monitoring memory consumption is still advisable. + +_Source threads: 1319559634453008415 · confidence: medium_ +_Date range: 2024-12-20_ +_Reviewer note: This is sourced from fafalone's statement in a Discord thread and marked medium confidence. Check whether the IDE memory leak has been fixed in a more recent twinBASIC release before publishing --- the statement dates from December 2024. The IDE-Features page covers IDE behavior generally and is the closest existing target; a dedicated Known Issues or Release Notes page would be a better long-term home if one exists._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The twinBASIC IDE does not currently support split editor windows or side-by-side source views. The entire editor surface is rendered as a single WebView2 HWND, so intercepting or extending it by subclassing the window handle is not practical. Side-by-side multiple editor windows are on the planned feature list. An addin workaround using the [tbIDE SDK](../../Reference/tbIDE/) can open a file as a read-only text panel in a tool window; the IDE ships sample 12, which demonstrates embedding a Monaco editor instance in a tool window via the addin SDK. + +_Source threads: 1379796923224952892 · confidence: high_ +_Date range: 2025-06-04_ +_Reviewer note: Verify the beta number (784) is still relevant or omit it. Confirm whether side-by-side windows have been added in any beta after 784. Relative link from IDE-Features.md to tbIDE/ index: ../../Reference/tbIDE/ — confirm against rendered URL._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> The code minimap and bracket pair colorization settings were removed from the IDE Options dialog and moved to the theme system. To enable them, create a `.theme` file in `C:\Users\\AppData\Roaming\twinBASIC\themes`, then select it via **Window > Theme**. A minimal file that enables both features while inheriting the built-in dark theme: +> +> ``` +> inherits: dark; +> CodeMinimapVisible: 1; +> CodeBracketPairColorization: 1; +> ``` +> +> Replace `dark` with `light` or `classic` to base the theme on one of the other built-in themes. Placing the file in the directory is not enough --- the theme must be selected explicitly in the IDE after it is created. + +_Source threads: 1381120139893674015 · confidence: high_ +_Date range: 2025-06-08_ +_Reviewer note: Addition targets the Theme System section in IDE-Features.md. Verify the AppData path and theme key names against the latest IDE build, as these were described in a Discord thread and not in official docs._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!WARNING] +> The **SafeMode** target (listed after `win64` in the target architecture dropdown) disables the compiler to allow raw file editing without diagnostic overhead. At least one report confirms that when SafeMode is active, the IDE does not detect file changes and does not save edits to disk. Switching back to a normal target causes all edits made during the SafeMode session to be lost. Switching the project to `win32` temporarily is a safer alternative when the goal is to suppress diagnostics during editing. + +_Source threads: 1382393005973180457 · confidence: medium_ +_Date range: 2025-06-11 to 2025-06-12_ +_Reviewer note: SafeMode behavior (edits not saved) may have been fixed in a subsequent IDE release. Verify against a current build before publishing. This warning could be promoted to a NOTE if the issue has been resolved._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +> [!NOTE] +> When an editor panel is floating (undocked), it can be returned to its docked position in two ways: drag it by the grabber control at the top-left corner of the floating window, or use the **Window** menu to apply any saved panel layout (such as the default layout). Closing and reopening the source file also restores the default dock state. + +_Source threads: 1391112464422404208 · confidence: high_ +_Date range: 2025-07-05_ +_Reviewer note: The grabber control description ("top-left corner") is from the maintainer's own reply; verify it still matches the current IDE layout before publishing._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +### Custom theme files + +Custom themes are plain text files with a `.theme` extension placed in `%APPDATA%\Roaming\twinbasic\themes`. A theme file can inherit from a built-in theme and override individual properties using the syntax `inherits: ;` followed by `PropertyName: value;` lines. + +The `InlineDecorationColor` property controls the color of the end-of-block tooltips shown at closing lines such as `End Sub` and `End If` in the editor. The value accepts any CSS color expression, including `rgba()` for semi-transparency: + +``` +inherits: Dark; +InlineDecorationColor: rgba(200, 200, 200, 0.5); +``` + +_Source threads: 1398426772021841961 · confidence: medium_ +_Date range: 2025-07-26 to 2025-08-06_ +_Reviewer note: The Theme System section in IDE-Features.md currently only has one sentence. Verify the property name InlineDecorationColor and the file format against an actual .theme file before publishing. The %APPDATA% path casing (twinbasic vs twinBASIC) should also be confirmed._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +### Align controls and snap to grid + +The Form Designer includes a snap-to-grid / align-controls feature. Hover over the designer toolbar area to reveal the **Enable align controls** button. When enabled, control movement is constrained to grid increments, which prevents accidental fine-grained shifts when clicking to select a control. + +### Locking controls + +Controls in the Form Designer can be locked to prevent accidental repositioning by mouse drag. To lock a single control, right-click it and choose **Lock** from the context menu. To lock all controls at once, press **Ctrl+A** to select all, then right-click and choose **Lock**. Locked controls remain selectable and their properties remain editable. To unlock, use the same context menu or **Edit > Unlock controls** / **Unlock all controls**. + +_Source threads: 1414851467046551552 · confidence: high_ +_Date range: 2025-09-09 to 2025-09-16_ +_Reviewer note: Merges two findings (snap-to-grid from thread 1414851467046551552 and lock controls from thread 1417476641105514707). Verify menu item names and exact toolbar button label against the current IDE build._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1432631707735031808, 1436747952189083799, 1448666378998382753] + +### Saving panel layout + +By default, the DEBUG CONSOLE, Variables, Diagnostics, and Call Stack panels reopen on each IDE start, even if they were closed in the previous session. To persist a custom panel arrangement, use **Save Panel Layout as** from the IDE's View or panel management menu. Once saved, that layout is restored on subsequent starts. + +_Source threads: 1414583335472070776 · confidence: medium_ +_Date range: 2025-09-08_ +_Reviewer note: Verify the exact menu path for 'Save Panel Layout as' in the current IDE build._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1436747952189083799, 1448666378998382753] + +## Form designer known issues + +> [!NOTE] +> Pressing the Alt key alone in the IDE (regardless of whether the form designer or code editor is active) opens the File menu. After the menu closes, the form designer may enter a broken state: clicking a control inside a container (Frame, PictureBox, or similar) selects both the child control and its container, and controls can no longer be moved with the mouse or arrow keys. Restarting the IDE clears the problem. +> +> Workaround: while in this state, use the control drop-down list at the top of the Properties pane to select the exact control whose properties need to be changed. The drop-down shows all controls available in the current context and is not affected by the selection state of the designer. + +_Source threads: 1432631707735031808 · confidence: high_ +_Date range: 2025-10-28_ +_Reviewer note: Merges two findings from the same thread (gotcha + workaround). Confirm the Properties pane drop-down description is accurate and that the Alt-key behavior has not been fixed in a post-October-2025 build._ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1448666378998382753] + +The following keyboard shortcuts operate on all procedures in the active editor at once: + +| Shortcut | Action | +|----------|--------| +| **Ctrl+Alt+Left** | Collapse all procedures except the one containing the cursor | +| **Ctrl+Alt+Right** | Expand all procedures | + +_Source threads: 1436747952189083799 · confidence: high_ +_Date range: 2025-11-08_ + +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799] + +### Code Formatting + +The **Format Document** command reformats the entire source file. It is available under **Edit** > **Format Document**. A **Format Selection** command reformats only the selected text. Both commands have default keyboard shortcuts that can be customized through the **Window** menu's keyboard shortcut manager. The internal command names displayed in the IDE status bar are `tbEditor_FormatDocument` and `tbEditor_FormatSelection`. + +_Source threads: 1448666378998382753 · confidence: high_ +_Date range: 2025-12-12_ + +--- + +## docs/Features/GUI-Components/Forms.md · after-remarks + +> [!NOTE] +> The `true` manifest element enables the "System (Enhanced)" DPI scaling mode in a compiled EXE, but has no effect when running under the twinBASIC IDE. To simulate the same mode during IDE execution, call `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)` in `Sub Main` before any forms are loaded. To check the current DPI context programmatically, call `GetDpiAwarenessContextForProcess(0)` and compare the result with `AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, lDpiContext)`. + +_Source threads: 1457732092715733173 · confidence: medium_ +_Date range: 2026-01-05 to 2026-01-06_ +_Reviewer note: Insert in the DPI Scaling section of Forms.md. The Win32 API declarations for SetProcessDpiAwarenessContext, GetDpiAwarenessContextForProcess, and AreDpiAwarenessContextsEqual are not in the docs; the reviewer may want to add them or link to the API declarations page._ + +--- + +## docs/Features/Language/Comments.md · after-remarks + +> [!NOTE] +> The block-comment lexer scans for the first `*/` sequence unconditionally, without recognizing string delimiters. If the character sequence `*/` appears inside a quoted string within a block comment, the comment ends at that point and the remaining text is parsed as twinBASIC source. For example: +> +> ```tb +> /* comment with "*/" more comment */ +> ``` +> +> The comment ends after the `*/` inside the string, not at the trailing `*/`. This is by-design behavior consistent with how most languages handle block-comment lexing. To avoid this, either rephrase the comment to avoid `*/` inside block comments, or use line comments (`'`) for code that contains such strings. + +_Source threads: 1162324498083745802 · confidence: high_ +_Date range: 2023-10-13_ + +--- + +## docs/Features/Language/Inheritance.md · after-remarks + +> [!NOTE] +> twinBASIC's object model is COM-based and inherits VB6's "object-based" rather than fully object-oriented architecture. Operator overloading is not supported, and **String** and array types are not first-class objects. OOP patterns that depend on these features --- such as arithmetic operators on custom numeric types --- cannot be replicated directly. The additions of generics, implementation inheritance (**Inherits**), and parameterized constructors (**Sub New**) go significantly beyond VB6 semantics, but the underlying system remains lower-level than C#, Java, or VB.NET. + +_Source threads: 1436287137350946816 · confidence: high_ +_Date range: 2025-11-07 to 2025-11-10_ + +--- + +## docs/Features/Language/Module-Organization.md · after-remarks + +> [!NOTE] +> Semantic highlighting in the IDE processes tokens near the visible area on each scroll event. In very large source files (roughly 60,000 lines or more), earlier IDE versions processed all tokens in the file on each scroll, causing choppy scrolling and high CPU usage. This was fixed in BETA 426, which introduced paging so that only the tokens close to the current view are processed. If working with an older version, splitting large files into smaller modules reduces the number of tokens processed per scroll. + +_Source threads: 1200262841345773578 · confidence: high_ +_Date range: 2024-01-26_ + +--- + +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1084723164296261672, 1283432466471845993] + +## Known Limitations + +> [!NOTE] +> The overload resolver has two known edge cases that can produce spurious ambiguity errors. +> +> **Specific interface types as the last parameter.** When two overloads differ only in parameter count but share the same trailing parameter type, the compiler resolves the call correctly when that type is `Object`. Changing the trailing type to a specific interface (such as a COM interface imported via a `Declare` or type-library reference) can cause the shorter overload to be reported as ambiguous, even though the argument counts differ unambiguously. Reversing the declaration order --- declaring the shorter overload with `Object` and the longer with the interface type --- avoids the error. +> +> **Implicit widening between `Long` and `Single`.** When one overload takes a single `Single` parameter and another takes two `Long` parameters, passing a `Long` variable to the one-argument call produces an ambiguity error because the resolver considers widening `Long` to `Single` as a candidate match for the two-argument form. Passing a `Long`-typed literal (e.g. `0&`) resolves without error because the explicit type suffix removes the widening candidate. + +_Source threads: 1081364889815228416 · confidence: medium_ +_Date range: 2023-03-03 to 2023-03-04_ +_Reviewer note: Both issues were reported by fafalone with minimal repro projects (Discord, early March 2023). Verify whether either has been fixed in later compiler builds before publishing. The first issue involves specific COM interface types as trailing overload parameters; the second involves Long-to-Single widening across overloads of different arity._ + +--- + +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1283432466471845993] + +> [!NOTE] +> +> All overloads of a given procedure name must be declared in the same module. The symbol resolver groups overloads by name within a module and registers them as a single entry; overloads defined in different modules are not recognized as overloads of each other and will instead cause an ambiguity error at the call site. This constraint matches VB6 behavior and is an intentional property of the context-free resolver. + +_Source threads: 1084723164296261672 · confidence: high_ +_Date range: 2023-03-13_ + +--- + +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1084723164296261672] + +> [!NOTE] +> Compiler error **TB5073** --- "Unable to disambiguate between overloaded methods" with the sub-message "*N* Incompatible matches found" --- does not mean the overloads cannot be distinguished from each other. It means the arguments at the call site are incompatible with every candidate overload. Even if each overload had a unique name, the call would still fail. The fix is to correct the argument types at the call site, not to restructure the overloads. + +_Source threads: 1283432466471845993 · confidence: high_ +_Date range: 2024-09-11 to 2024-09-12_ + +--- + +## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1245329874777014302] + +## Overload Resolution and Interface Types + +twinBASIC's overload resolution evaluates each candidate overload by testing all arguments. A candidate is eliminated when any argument requires a narrowing conversion to match the candidate's declared parameter type. The process succeeds when exactly one candidate remains. + +Interface types use stricter matching than value types. This is intentional: it lets code declare overloads that target different specific COM interface types --- for example, one overload for `IStream` and a separate overload for `ISequentialStream` --- where traditional VBx would accept any interface at the call site and only fail at runtime. + +> [!NOTE] +> Because all arguments are checked before a candidate is eliminated, an overload can be incorrectly discarded even when another argument (such as `String` vs `LongPtr`) already uniquely identifies it. If every candidate is eliminated, the compiler reports **TB5073** (unable to disambiguate) even though the intent is unambiguous. + +**Passing `Nothing`.** Prior to BETA 536, passing `Nothing` to a parameter declared as a specific interface type (e.g., `IBindCtx`) was treated as a narrowing conversion, eliminating that candidate. When all overloads shared the same specific-interface parameter, every candidate was eliminated and TB5073 was reported. BETA 536 corrects this: `Nothing` is treated as non-narrowing for any interface parameter. + +**Passing a base-interface variable.** Passing a variable typed as a base interface (e.g., `IUnknown`) to overloads that all expect more derived interface types still results in TB5073. The compiler does not yet traverse the COM interface inheritance chain to find the best match. The workaround is to cast the variable to the exact interface type required by the intended overload before passing it: + +```tb +' Suppose two overloads exist: +' Sub Foo(pbc As IBindCtx, pszPath As String) +' Sub Foo(pbc As IBindCtx, pszPath As LongPtr) + +Dim unk As IUnknown +' This causes TB5073 -- IUnknown is narrowing to IBindCtx: +' Foo unk, "C:\example" + +' Cast first to avoid the ambiguity: +Dim bc As IBindCtx +Set bc = CType(unk, IBindCtx) +Foo bc, "C:\example" +``` + +_Source threads: 1238767956050051114 · confidence: high_ +_Date range: 2024-05-11 to 2024-05-12_ +_Reviewer note: The `Nothing`-as-non-narrowing fix is confirmed for BETA 536 by WaynePhillipsEA in the thread. The IUnknown / base-interface limitation was confirmed as not yet implemented as of the thread date (2024-05-12). Verify whether COM interface inheritance traversal has since been added before publishing._ + +--- + +## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1238767956050051114] + +## Overload resolution and widening conversions + +When no candidate overload is an exact match for every argument --- that is, every candidate requires at least one widening conversion for at least one argument --- the compiler cannot choose between them and reports an ambiguity error. It does not narrow the candidate set by comparing individual parameter types across overloads and then resolve the remainder independently. + +A common trigger is passing an unqualified integer literal where a **Long** parameter is declared. The literal is typed as **Integer** by default, so the call requires a widening conversion to **Long**, and if another overload also requires a widening conversion for a different parameter, neither candidate is preferred. + +The fix is to use a typed literal suffix so the argument matches the intended overload exactly: + +```tb +' Ambiguous: 1 is Integer; both overloads need a widening conversion +Foo 1, "hello" ' error if Foo(Long, String) and Foo(Integer, Long) both exist + +' Unambiguous: 1& is Long; matches the Long overload exactly +Foo 1&, "hello" +``` + +See [Features → Literals](/Features/Language/Literals) for the full list of typed literal suffixes. + +_Source threads: 1245329874777014302 · confidence: high_ +_Date range: 2024-05-29 to 2024-05-30_ +_Reviewer note: The Overloading.md page is a feature overview page, not a reference page. Confirm the Literals page permalink /Features/Language/Literals exists and covers typed literal suffixes (e.g. 1& for Long). If not, adjust the See Also link._ + +--- + +## docs/Features/Packages/Creating a TWINPACK package.md · after-remarks + +> [!NOTE] +> Older tutorials and documentation refer to a dedicated **TWINBASIC PACKAGE MANAGER** panel inside the main Explorer activity panel. That panel was removed in a subsequent IDE redesign. The equivalent publishing controls are now under **View** > **Package Publishing**, which controls whether the package is published to TWINSERV as public or private. To produce a local `.twinpack` file for use in other projects, use the regular **Build** command; the output path is shown in the debug log and is configurable in project settings. + +_Source threads: 1312685090232471612 · confidence: high_ +_Date range: 2024-12-01_ +_Reviewer note: Verify the exact menu path (View > Package Publishing) against the current IDE. The page already shows a screenshot of the PACKAGE PUBLISHING panel as a popup --- confirm whether the screenshot is current or needs updating._ + +--- + +## docs/Features/Packages/Import-export tool.md · after-remarks + +> [!NOTE] +> The **Export on Save** setting writes individual source files to a folder on disk, which makes the exported files suitable for version control. However, the export folder is overwritten in full on every save, so placing a `.git` directory directly inside it will destroy the repository. The recommended approach is to export into a dedicated subfolder within a broader git repository, keeping the `.git` directory and any other project assets outside that subfolder. Git will not record spurious changes when unchanged files are replaced with identical content. + +_Source threads: 1314117578024812604 · confidence: high_ +_Date range: 2024-12-05 to 2025-01-30_ +_Reviewer note: This gotcha is currently undocumented. The Import-export tool page is a reasonable location but the finding is really about the IDE's built-in Export on Save feature rather than the standalone command-line tool. A more precise home would be a dedicated page on version-control workflows if one is added, or the project-settings / project-configuration section. Reviewer should consider whether to place this note here or on a more targeted page._ + +--- + +## docs/Features/Packages/Import-export tool.md · new-section + +## Version control via the IDE + +The IDE provides a built-in path for version-controlling a twinBASIC project without the command-line tool. Enable the **Auto export to files on save** option in the project settings; the IDE then writes each module as an individual file alongside the `.twinproj` file on every save. The exported files are plain text and diff cleanly in Git, SVN, or any other version-control system. + +To reconstruct a project from a set of exported files --- for example after cloning a repository --- use the **Import from folder** option on the IDE start-up screen. This option appears only on the initial dialog before a project is open; it is not available from inside an already-open project. + +> [!NOTE] +> Full external-file support --- where the `.twinproj` file references files on disk directly, analogous to a `.vbp` file --- is planned for a future release but is not yet available. + +_Source threads: 1302320508800602153 · confidence: high_ +_Date range: 2024-11-02 to 2024-11-06_ +_Reviewer note: Verify the exact name of the project-settings option ("Auto export to files on save" is paraphrased from Discord). Confirm that "Import from folder" is still on the startup screen and not elsewhere in a newer IDE build._ + +--- + +## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1333426890291023884] + +> [!NOTE] +> After importing a `.twinpack` file, the package appears in the Project Explorer under **Packages** but its symbols are not yet active. Check the checkbox next to the package in the **Available Packages** list (the same list used to import it), then save **Settings** to activate it. Skipping this step is a common source of unresolved-symbol errors --- the package is present but inactive until the box is checked. + +_Source threads: 1312685090232471612 · confidence: high_ +_Date range: 2024-12-01_ + +--- + +## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1312685090232471612] + +> [!IMPORTANT] +> The 'Import from file...' button must be clicked while the **Packages** tab (also labelled 'Available Packages') is active in the project settings. If the button is clicked while the **References** tab or any other tab is shown, twinBASIC treats the `.twinpack` file as a COM type library and fails with error `0xc000012f`. The error message does not indicate the tab-selection cause. + +_Source threads: 1333426890291023884 · confidence: high_ +_Date range: 2025-01-27_ +_Reviewer note: The existing page refers to a 'References section' / 'Available Packages' button flow that may reflect an older UI. Verify the current tab labels in the IDE before merging, and update the main page text if needed._ + +--- + +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1427261753544409168, 1460777854714515728, 1495944076632002572] + +> [!NOTE] +> A `.twinproj` file placed in `%APPDATA%\Roaming\twinBASIC\packages` also appears in the **Available Packages** list and can be referenced as a linked package. When adding the reference, leave the **Embedded** checkbox unticked so the reference stays linked rather than copying the source into the referencing project. Changes made to the shared project are visible to referencing projects the next time they are loaded. This pattern is the recommended approach for sharing code across multiple twinBASIC projects without duplicating it. + +_Source threads: 1392532961366376469 · confidence: high_ +_Date range: 2025-07-11_ +_Reviewer note: The existing Linked Packages page describes placing .twinpack files in %APPDATA%\Roaming\twinBASIC\packages. Verify that .twinproj files placed there also appear in Available Packages before publishing._ + +--- + +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1460777854714515728, 1495944076632002572] + +> [!NOTE] +> +> When sharing a `.twinproj` file that uses a linked package, the recipient must have that package registered on their machine. If the package is missing, open the project, uncheck (remove) the broken reference in the Available Packages list, then re-add it and uncheck the Embedded column to restore the linked reference. The "Fix" option is not currently implemented. + +_Source threads: 1427261753544409168 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: The existing 'Opening a project with missing linked package' section already covers this workflow. This note may be redundant; reviewer should decide whether it adds enough clarity to keep, or whether it should simply be removed in favour of the existing section._ + +--- + +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1495944076632002572] + + +_Source threads: 1460777854714515728 · confidence: medium_ +_Date range: 2026-02-15_ +_Reviewer note: The finding notes that linked packages keep project file size small by avoiding embedding a dependency's type, enum, and Declare definitions. This is already covered by the existing Linked Packages page (the benefit of not storing a copy in every .twinproj and sharing from a common location). No new content is needed; this addition is a no-op and can be discarded._ + +--- + +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1460777854714515728] + +## Fixing a failed package reference + +When a package reference fails to load --- shown as "Unable to load this reference" in the project settings --- re-selecting the same package in the References dialog does not repair the reference. The correct sequence is: + +1. Clear the broken reference by unchecking it. +2. Click **Apply Changes** to commit the removal. +3. Reopen References, go to the **Available Packages** tab, and select the package again. + +Attempting to re-add without first clearing and applying leaves the stale entry in place. + +> [!NOTE] +> A very large number of compile errors (hundreds) when opening a project is a common symptom of a missing package reference, not a code defect. If the project uses a linked package that has not been added as a reference, the compiler reports one error per unresolved identifier. Adding the package under **Project -> References -> Available Packages** clears them all. + +_Source threads: 1495944076632002572 · confidence: high_ +_Date range: 2026-04-05 to 2026-04-21_ + +--- + +## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1368264887448371372] + +> [!NOTE] +> `.twinproj` files produced by the twinBASIC IDE can legitimately contain a mix of CRLF and LF line endings within the same file, even without any third-party tooling involved. This is a known characteristic of the format and causes no functional issues. When tracking `.twinproj` files in git, configure them as binary to prevent git from normalizing line endings: +> +> ``` +> # .gitattributes +> *.twinproj binary +> *.twinpack binary +> ``` + +_Source threads: 1397374588056371350 · confidence: medium_ +_Date range: 2025-07-23_ +_Reviewer note: TWINPACK File Format page is a binary-format spec. This git note may fit better in docs/Features/Packages/Linked Packages.md or a new version-control tips section. Reviewer should confirm the best placement._ + +--- + +## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1397374588056371350] + +> [!NOTE] +> The `.twinproj` file is a binary container format and is not a plain-text file. A text editor may display some readable text within it, but the file is not suitable for direct editing or syntax highlighting. To work with project source files in an external editor, use the [Import/Export Tool](Import-Export-Tool) to unpack the project to a directory of `.twin` (and `.bas`, `.cls`) plain-text source files. After editing those files externally, reopen the original `.twinproj` file in twinBASIC to continue work. + +_Source threads: 1368264887448371372 · confidence: high_ +_Date range: 2025-05-06_ + +--- + +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1201636475306000536, 1210853350707429406, 1301076697642438656] + +> [!NOTE] +> When a project contains two packages that have a dependency relationship --- package B depends on package A --- and the version of package A in the project does not match the version that package B was compiled against, the compiler reports 'unrecognized symbol' errors on symbols from package A when they appear inside package B. The error message gives no indication that a version conflict is the cause. If unexpected 'unrecognized symbol' errors appear on symbols from a package that other packages depend on, check that all inter-dependent packages are at mutually compatible versions and update the dependent package (B) to a version compiled against the currently installed version of package A. + +_Source threads: 1171155850832908318 · confidence: medium_ +_Date range: 2023-11-06 to 2023-11-07_ +_Reviewer note: This page is in docs/Features/Packages/ and is not in page-index.json (which covers only Reference docs). The finding has package='Core', symbol=null -- no Reference page covers package version compatibility errors. Placement here is an editorial judgement; a reviewer may prefer a different Features page or a new troubleshooting section. Also verify whether the IDE now emits a clearer error message for this case._ + +--- + +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1210853350707429406, 1301076697642438656] + +> [!NOTE] +> In IDE versions before BETA 444, removing a package reference in the Settings dialog could leave the IDE in a locked state. The secondary confirmation prompt ("also remove the imported package from the filesystem") appeared behind the Settings modal; after responding to it the modal was not restored, and the IDE behaved as though the dialog was still open. In some cases the package contents were deleted from the project file while the reference entry remained, producing a project file that could not be opened normally. Projects that reach this corrupt state can be recovered by opening the project in safe mode, which makes the Settings dialog accessible so the orphaned reference can be removed. This issue was fixed in BETA 444. + +_Source threads: 1201636475306000536 · confidence: high_ +_Date range: 2024-01-29 to 2024-02-10_ +_Reviewer note: Target page is in Features/Packages (not in the page-index), chosen as the best fit because it explicitly covers the remove-package workflow. The safe-mode recovery path should be verified against current IDE capabilities --- older IDE versions had a safe mode option; confirm it still exists or adjust wording accordingly._ + +--- + +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1301076697642438656] + +> [!NOTE] +> Unticking a package reference in Project Settings disables the package --- the IDE removes the reference and makes the package's symbols unavailable to the compiler --- but does not immediately delete the locally cached package files. A second dialog then appears offering two choices: **Remove it** deletes the cached files from disk, and **Leave it** keeps them. Choosing **Leave it** lets you re-enable the same version later without re-downloading. To upgrade to a newer version, you must choose **Remove it** so the cache is cleared before importing the updated package from the package server. + +_Source threads: 1210853350707429406 · confidence: high_ +_Date range: 2024-02-24 to 2024-02-27_ + +--- + +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1210853350707429406] + +> [!NOTE] +> The new-version notification can be turned off in the IDE settings. One-click automatic update to the latest package version is planned for a future release; the current workflow requires removing the old package and re-importing the newer one as described below. + +_Source threads: 1301076697642438656 · confidence: high_ +_Date range: 2024-10-30 to 2024-11-18_ +_Reviewer note: Confirm the exact settings path in the IDE where the notification can be disabled, and verify whether the opt-out affects all packages or per-package._ + +--- + +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1359918618573275286, 1503959771949301922] + +> [!NOTE] +> Packages installed into a project are opened as read-only in the code editor and cannot be edited directly, similar to a referenced type library in VB6. To customise package code, export and import the source into regular project code, or rebuild the package from its `.twinproj` source file if available. If a symbol from a package is copied into the project under the same name, the project's own version takes priority at call sites --- there is no conflict, and the project copy is called unless the call site explicitly qualifies with the package name. + +_Source threads: 1285758420326027407 · confidence: medium_ +_Date range: 2024-09-18_ + +--- + +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1503959771949301922] + +> [!NOTE] +> All twinBASIC packages --- including the built-in runtime packages --- are currently distributed as source code, not pre-compiled binaries. When a package is added to a project, it is compiled once at load time and its contents appear as read-only in the project explorer; the source cannot be edited from within the consuming project. A package does not need to be self-contained or independently compilable: it may contain only interfaces, API declarations, or abstract classes that require the consuming project to supply implementations. +> +> Building a package via **File > Build** (or the toolbar) produces a `.twinpack` file locally but does not upload anything. Uploading to the package server requires the **Package Publishing** panel, where visibility (public or private) can also be set. Compiled packages --- distributing binaries without exposing source --- are planned for a future release. + +_Source threads: 1359918618573275286 · confidence: high_ +_Date range: 2025-04-10 to 2025-04-15_ +_Reviewer note: The page already mentions that TWINPACK files contain full source code in the last paragraph. The new note expands on the read-only behavior in the consuming project and the publishing workflow. Merge with or replace that existing paragraph to avoid duplication._ + +--- + +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1359918618573275286] + +> [!NOTE] +> twinBASIC does not yet support referencing `.bas` or `.cls` source files stored outside the `.twinproj` project file in a way that keeps them linked rather than copied. The recommended approach for sharing modules across projects is to package them into a private twinBASIC package and reference that package from each consuming project. Note that module files in a referenced package are read-only within the consuming project, and changes to the package source are not reflected until the package is recompiled and re-imported. + +_Source threads: 1503959771949301922 · confidence: high_ +_Date range: 2026-05-13 to 2026-05-18_ + +--- + +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1117993757170749490, 1261148532157714432, 1388075026892455987] + +> [!NOTE] +> Automatic registration occurs immediately after every build. The **Project: Register DLL after build** option in the project settings controls whether this registration step runs. Disabling it is useful when the binary needs to be copied to a different location before registering, for example during deployment to a test machine. + +_Source threads: 1073568869106597938 · confidence: high_ +_Date range: 2023-02-10_ +_Reviewer note: The existing ActiveX-Registration.md page already documents the 'Register DLL after build' option and says registration is optional. The Discord finding (from early 2023) states the option did not exist at that time. Verify whether this NOTE adds anything not already covered by the existing page content, and adjust or drop accordingly._ + +--- + +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1261148532157714432, 1388075026892455987] + +> [!NOTE] +> If a host application (such as Microsoft Access) crashes at its own compile step after a reference to a twinBASIC COM DLL is added, the tB code is not executing at that point. The host is parsing the DLL's type library during its own project compilation. The type library contains an entry the host cannot parse. To diagnose the problem, progressively remove public exports from the tB project until the crash stops, then identify which declaration is incompatible. Also verify that the host and the DLL share the same processor architecture: a 32-bit host cannot load a 64-bit DLL, even when the reference is successfully added. + +_Source threads: 1117993757170749490 · confidence: high_ +_Date range: 2023-06-13 to 2023-06-15_ +_Reviewer note: The page-index does not include docs/Features/ pages. Reviewer should confirm placement in docs/Features/Project-Configuration/ActiveX-Registration.md or consider docs/Features/Project-Configuration/Project-Types.md. The finding is specifically about COM ActiveX DLL type-library compatibility with VBA host applications._ + +--- + +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1388075026892455987] + +> [!IMPORTANT] +> When a twinBASIC ActiveX DLL is built or registered by a non-elevated process, its registration goes to `HKEY_CURRENT_USER` by default. If VB6 (or any other host) is then launched with elevated (administrator) privileges, it cannot see that per-user registration and reports a "Class not registered" error. To avoid this, set the project's registration target to `HKEY_LOCAL_MACHINE` in the project settings, or build and register the DLL from an elevated instance of twinBASIC. Manually registering the DLL from an elevated command prompt alone is not sufficient if the project setting still points to `HKEY_CURRENT_USER` --- the per-user registration is written again on the next build. + +_Source threads: 1261148532157714432 · confidence: high_ +_Date range: 2024-07-12_ + +--- + +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1261148532157714432] + +> [!NOTE] +> When consuming a twinBASIC ActiveX DLL from Microsoft Access or Excel VBA, declare the object using the fully qualified form `ProjectName.ClassName`, not `ClassName` alone. Using the class name without the project prefix produces the error "Expected user-defined type, not project". For example, if the project is named `MyLib` and the class is `MyClass`, the declaration in the host VBA module must read: +> +> ```vb +> Dim obj As New MyLib.MyClass +> ``` +> +> This is standard COM early-binding behavior. + +> [!NOTE] +> twinBASIC does not support interactive cross-project debugging of an ActiveX DLL from a host application such as Access or Excel. Breakpoints and step-through from the host are not available. The recommended approach is to copy the DLL classes into a Standard EXE project and debug there. For runtime diagnostics in the compiled DLL, use `Debug.TracePrint` with the trace logger rather than manual log-file writes. + +_Source threads: 1388075026892455987 · confidence: high_ +_Date range: 2025-06-27_ + +--- + +## docs/Features/Project-Configuration/Compiler-Options.md · after-remarks + +## Stack Reserve Size + +The twinBASIC non-optimizing compiler allocates larger stack frames than VB6, so recursive procedures exhaust the call stack much sooner than an equivalent VB6 project. The project setting **Build Stack Reserve Size** (default 1 MB) controls the stack reserve for compiled EXEs. Increasing it to 4 MB or 16 MB allows substantially deeper recursion; for 64-bit processes the additional reserve is only committed as needed and carries no practical memory cost at rest. + +> [!NOTE] +> Stack frame sizes are expected to decrease significantly once LLVM-based code generation is fully operational. + +_Source threads: 1406596075032412234 · confidence: high_ +_Date range: 2025-08-17_ +_Reviewer note: The Compiler Options page currently does not mention stack size at all. Verify the exact setting name ('Build Stack Reserve Size') against the twinBASIC IDE project settings UI before publishing._ + +--- + +## docs/Features/Project-Configuration/Compiler-Options.md · new-section + +## LLVM Optimization (Professional) + +The Professional tier adds an LLVM-based optimization pass on top of the standard code generator. The gains are workload-dependent: + +- **Where LLVM helps most:** tight numeric or algorithmic loops in standard modules that avoid strings, Variants, classes, interfaces, and Windows API calls in the hot path. In those cases the optimizer can inline aggressively, unroll loops, fold constants, and remove redundant checks, giving large multipliers over the baseline. +- **Where LLVM helps less:** code whose hot path calls runtime string functions (`Mid$`, `InStr`, `Left$`, etc.), passes strings `ByVal`, or dispatches through COM objects. The bottleneck in those cases is the runtime or the API, not the compiled glue code, so gains are small. + +As of late 2025 the LLVM backend was partially implemented and 32-bit only; full 64-bit coverage is planned for v1.0. + +### Performance relative to VBA and VB6 + +Without LLVM, twinBASIC compiled code is faster than VBA and VB6 compiled to p-code. Results against VB6 native-compiled and speed-optimized executables are mixed and workload-dependent --- computation-heavy code (math, array operations) has been measured 25--30% faster in twinBASIC by independent users, while simple micro-benchmarks with default settings sometimes show VB6 native ahead. The twinBASIC maintainer has confirmed that the standard benchmark suite runs faster in a twinBASIC EXE than in VBA. + +_Source threads: 1392249968852471899 · confidence: high_ +_Date range: 2025-06-30 to 2025-12-01_ +_Reviewer note: LLVM 64-bit status and v1.0 timeline should be verified against the latest roadmap before publishing, as the thread data is from late 2025 and the situation may have changed._ + +--- + +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1113070283004313701, 1380597011354484738, 1415716215204085864, 1220612763873972314] + +> [!NOTE] +> The `Console` class added to a new Console Application project is a minimal stub. It does not include a `Write` method by default. To use `Console.Write`, add the method manually by editing `Console.twin` in the project. + +_Source threads: 1109285180759347321 · confidence: high_ +_Date range: 2023-05-20 to 2023-05-23_ +_Reviewer note: Verify the current state of the Console.twin stub shipped with new Console Application projects -- the set of methods it provides may have changed since this Discord report._ + +--- + +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1380597011354484738, 1415716215204085864, 1220612763873972314] + +> [!NOTE] +> +> Prior to twinBASIC BETA 334, calling `SetConsoleMode` (or any function that calls it internally, such as `Console.ReadChar`) from code running inside the IDE returned 0 and set `Err.LastDllError` to 5 (`ERROR_ACCESS_DENIED`). The same code ran correctly in a compiled `.exe`. This discrepancy was a known IDE limitation and has been resolved as of BETA 334. + +_Source threads: 1113070283004313701 · confidence: high_ +_Date range: 2023-05-30 to 2023-06-15_ +_Reviewer note: Confirm that BETA 334 is the correct fix version and that Console.ReadChar is the right example of an affected built-in function. The fix was described by WaynePhillipsEA on Discord._ + +--- + +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1415716215204085864, 1220612763873972314] + +> [!NOTE] +> A **Standard EXE** that uses a `Sub Main` entry point instead of a startup form is not a Console App. The **Is Console App** setting in Project Settings is what makes a project a console application --- not the presence of a `Main` sub. To create a Standard EXE that starts from `Main` without a default form: start from the Standard EXE template, add a module (which provides the `Main` sub), then delete the default form. Starting from the Console App template and removing its console-specific code leaves the **Is Console App** flag set, which causes the Debug Console window to appear whenever a form editor is opened. + +_Source threads: 1380597011354484738 · confidence: high_ +_Date range: 2025-06-06_ + +--- + +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1220612763873972314] + +## Startup object + +A standard executable project requires one of two startup configurations, set in the project settings: + +- **Sub Main** --- a `Public Sub Main()` procedure in a module. The IDE calls this procedure when the executable starts. Use this for non-GUI applications or when startup logic needs to run before any form is displayed. +- **Startup Form** --- a form designated as the startup object. The IDE displays that form when the executable starts, and the application ends when all forms are closed. + +If neither is configured the project has no valid entry point and cannot run. + +_Source threads: 1415716215204085864 · confidence: medium_ +_Date range: 2025-09-11_ +_Reviewer note: Basic project setup fact; verify exact UI label and setting location in the current IDE. The Project-Types.md page focuses on advanced project types; a general Startup Object section may fit better in Project-Configuration/index.md or a dedicated page._ + +--- + +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1415716215204085864] + +### Control Panel Applets + +A Standard DLL project can produce a classic Windows Control Panel applet (`.cpl`) by exporting a function named `CPlApplet`. Classic `.cpl` applets are still recognized and loaded by Windows 10 and Windows 11. + +When the applet's dialog uses ComCtl6-style controls but is loaded by a host process that does not provide a ComCtl6 activation context manifest (which `control.exe` does not), the DLL must call `CreateActCtx` and `ActivateActCtx` before creating each ComCtl6 window, then `DeactivateActCtx` afterward. All windows created while the context is active inherit the ComCtl6 theme. Resources within the DLL are loaded using the DLL's own module handle rather than the host's. + +_Source threads: 1220612763873972314 · confidence: medium_ +_Date range: 2024-03-22_ +_Reviewer note: This is a Features page, not a Reference page. Verify that the Standard DLL section of Project-Types.md is the right home, or whether a dedicated .cpl tutorial page would be more appropriate. The ComCtl6 activation context pattern applies generally but is described here in the .cpl context._ + +--- + +## docs/Features/Standard-Library/Unicode-Support.md · after-remarks + +## Unicode characters in string literals + +Because `.twin` files store source code as full Unicode, pasting code from a source that uses Unicode curly apostrophes (U+2018 / U+2019, the typographic single-quote pair) instead of the plain ASCII apostrophe (U+0027) produces string literals that contain non-ASCII characters. WMI queries and SQL `WHERE` clauses that rely on ASCII apostrophes as delimiters fail at runtime with an 'Invalid query' error when the literal contains these characters. + +The fix is to replace the Unicode apostrophes with standard ASCII apostrophes (U+0027) in the source. VB6 silently substituted a backtick in the same situation; twinBASIC preserves the original Unicode character, making the problem visible only at runtime. + +_Source threads: 1233612072835616871 · confidence: high_ +_Date range: 2024-04-27_ + +--- + +## docs/IDE/Debug Console.md · after-remarks + +> [!NOTE] +> The IDE execution log may include messages of the form **IDE internal error: Uncaught ReferenceError: *name* is not defined** or **IDE internal error: Uncaught TypeError: Cannot set properties of null**. These are internal diagnostics intended for the IDE developer, not indicators of a problem in user code. As long as the IDE does not halt or freeze, they can be ignored. +> +> In very large projects an internal overflow in the IDE's error-reporting path can cause the line number shown for a genuine runtime error to be incorrect. The actual failing line may differ significantly from the reported location. When a runtime error occurs and the reported line appears unrelated to the surrounding code, treat the line number as unreliable and inspect a broader area---particularly any recently changed code---to locate the real fault. + +_Source threads: 1312778660079730690 · confidence: high_ +_Date range: 2024-12-01 to 2024-12-02_ +_Reviewer note: Content is based on WaynePhillipsEA's direct explanation in the Discord thread. No .twin source to verify against --- this is IDE operational behavior. Confirm that the execution log shown in the thread (timestamped [SHELL] output) surfaces in the Debug Console rather than a separate IDE log pane before placing this addition here._ + +--- + +## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1313837849690378280] + +> [!NOTE] +> When a method call is inserted by IntelliSense tab-completion (a snippet insertion), typing `)` or `]` after the insertion adds a second closing character instead of moving past the one already inserted. The skip-over mechanism --- which normally lets `)` advance the cursor past an auto-inserted `)` --- only activates when Monaco itself recorded the corresponding `(` keystroke. Tab-completed snippets do not trigger that recording, so the mechanism does not activate. This is a Monaco editor limitation. Use the arrow key to move past the auto-inserted character, or delete the extra one with **Backspace**. + +_Source threads: 1200413549567156324 · confidence: high_ +_Date range: 2024-01-26 to 2024-02-10_ +_Reviewer note: Confirmed by the twinBASIC developer (WaynePhillipsEA) as a Monaco limitation as of February 2024. Verify whether a fix has been shipped in a later release before publishing._ + +--- + +## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1200413549567156324] + +> [!NOTE] +> +> The code editor uses the Monaco editor engine, which may render certain fonts that are not strictly monospaced (such as MeowSans) with incorrect cursor or caret positioning. The same fonts render correctly in a native Windows text editor. To correct cursor alignment for such fonts, enable font ligatures in the IDE Options dialog (Tools > IDE Options..., Editor section, **Font Ligatures** toggle). This option was added in twinBASIC BETA builds from late December 2024 onwards. In older builds without this option, open `ide/main.js`, find the call `editor.updateOptions({fontFamily:n})`, and change it to `editor.updateOptions({fontFamily:n,fontLigatures:true})`. + +_Source threads: 1313837849690378280 · confidence: high_ +_Date range: 2024-12-04 to 2024-12-29_ +_Reviewer note: The Editor.md page currently lists editor options but does not include a Font Ligatures entry. Verify whether this option now appears in the IDE Options dialog and update the option list accordingly._ + +--- + +## docs/IDE/Editor.md · new-section + +## Troubleshooting + +> [!NOTE] +> If code in a `.twin` file loses syntax colouring --- all text appears uncoloured --- the most common cause is that the background compiler process has crashed. The IDE continues to function but cannot produce colour information. To restore highlighting, use the **Restart the compiler** button on the toolbar. The Debug Console or the status bar at the bottom of the IDE may show additional information when this happens. If restarting the compiler does not help, some users have also resolved the issue by recreating the project file or moving it to a shorter file path. + +_Source threads: 1053718492647522314 · confidence: medium_ +_Date range: 2022-12-17_ +_Reviewer note: Verify that the 'Restart the compiler' toolbar button name matches the current IDE UI text. The claim about short file paths as an alternative fix came from user reports and may be version-specific._ + +--- + +## docs/IDE/FindReplace.md · after-remarks + +> [!NOTE] +> The Find/Replace dialog retains a history of previously entered search strings. Pressing the up or down arrow key inside the **Find What** field cycles through earlier queries, avoiding the need to retype recent searches. + +_Source threads: 1478833214532620381 · confidence: medium_ +_Date range: 2026-03-04_ +_Reviewer note: The page-index does not include IDE pages. Reviewer should confirm the path docs/IDE/FindReplace.md and verify the feature is present in the current IDE build before publishing._ + +--- + +## docs/IDE/History.md · new-section + +## Troubleshooting + +If the History panel stops responding, or if switching panel layouts repeatedly produces errors such as "internal error: unable to close floating panel" or "couldn't close hidden panel", the IDE's stored panel layout may be corrupted in the Windows registry. + +The IDE stores panel layout state under: + +``` +HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings +``` + +Three values in that key control the layout: + +| Value | Purpose | +|-------|---------| +| `LAYOUTv2` | The active panel layout | +| `CUSTOM_PANEL_LAYOUTS` | All saved custom panel configurations | +| `LAYOUT` | Legacy layout entry | + +Deleting all three values and restarting the IDE resets the layout to its defaults. The IDE regenerates these entries on start-up. Any custom layout previously exported as JSON from within the IDE can be re-imported afterward. + +> [!NOTE] +> This issue is machine-specific and does not reproduce in clean environments. If it recurs, export any custom layouts as JSON before deleting the registry values so they can be restored. + +_Source threads: 1230091088560128061 · confidence: high_ +_Date range: 2024-04-17 to 2024-04-23_ +_Reviewer note: The History page is not in the page-index (package: tbIDE, symbol: null). The content concerns the IDE's panel layout registry, not the tbIDE addin SDK — docs/IDE/History.md is the closest existing page. Verify that the registry path is still current and that the value names (LAYOUTv2, CUSTOM_PANEL_LAYOUTS, LAYOUT) match the shipping IDE._ + +--- + +## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1207651928356552704] + +> [!NOTE] +> Dragging a panel by its title bar detaches it into a floating window. To move a panel to a different position within the docked layout without detaching it, drag the six-dot gripper icon at the left end of the panel's title bar and drop it onto the target docked position. To return all panels to their original positions, use **Window** > **Panel Layouts** > **Default Built-in Layout**. + +_Source threads: 1209855011530743908 · confidence: high_ +_Date range: 2024-02-21_ +_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Panel Layouts section of the Window menu page._ + +--- + +## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1209855011530743908] + +> [!NOTE] +> The `tbEditor_DeleteLines` command deletes the current line (or all selected lines) in the code editor. Its default keyboard shortcut is Ctrl+Shift+Delete. This command was added in BETA 451. The shortcut can be viewed and reassigned through **Window** > **Keyboard Shortcuts** > **Manage Keyboard Shortcuts**. + +_Source threads: 1207651928356552704 · confidence: high_ +_Date range: 2024-02-15 to 2024-02-16_ +_Reviewer note: Reviewer should confirm placement -- this note belongs near the Keyboard Shortcuts section of the Window menu page, alongside the `tbEditor_DeleteLines` entry in the default shortcuts JSON._ + +--- + +## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1203088725605621801] + +> [!NOTE] +> Importing a VB6 project that embeds the Windows Media Player ActiveX control (`WMPLibCtl.WindowsMediaPlayer`, from `wmp.dll`) fails at load time with a JSON deserialization error, even though the reference import step reports success. The control is embedded in `.ctl` form files using a `Begin WMPLibCtl.WindowsMediaPlayer` block, and twinBASIC cannot parse the control's serialized property data. The project will not load correctly until the Windows Media Player control is removed or replaced with an alternative. + +_Source threads: 1109156485201661972 · confidence: medium_ +_Date range: 2023-05-19_ +_Reviewer note: Finding is tagged package=VB, symbol=null. No dedicated VB6-import page exists; placed on docs/IDE/New Project.md as the closest page covering 'Import from VBP...'. A more appropriate home would be a dedicated VB6 migration / known-limitations page if one is created. The JSON deserialization error was reported by a community user in May 2023 and may have been fixed since then -- verify against a current IDE build before publishing._ + +--- + +## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1109156485201661972] + +> [!NOTE] +> The New Projects dialog uses an invisible modal overlay that blocks all interaction with the main IDE window while the dialog is open. If the dialog opens positioned so that its close button is off-screen or otherwise inaccessible, the IDE can appear completely unresponsive. Press **Escape** to dismiss the dialog and restore access to the main window. +> +> Starting from BETA 438, the IDE includes a configurable option to display a real OS-native titlebar. Enabling it reduces the likelihood of this situation, because the OS titlebar remains draggable independently of any modal overlay. + +_Source threads: 1203088725605621801 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-06_ +_Reviewer note: Target page is docs/IDE/New Project.md, which is not in the page-index (the finding package is tbIDE with null symbol). The page content is minimal and has no existing remarks section -- reviewer should confirm placement and that BETA 438 native-titlebar option description is still accurate._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> Export can intermittently fail with a message such as "failed to create output file: `` (check permissions and storage space)" or "failed to clean directory", even when disk space and permissions are not the cause. The underlying reason is a Windows file-system behavior: when the IDE removes the export output folder before writing a fresh copy, Windows queues the deletion rather than completing it immediately. The folder still appears in the directory listing for an indeterminate period; any attempt to recreate, rename, or write into it during that window fails silently. Once Windows processes the deletion, the path becomes available again. Retrying the export immediately --- without making any other changes --- typically succeeds. + +_Source threads: 1065982376154501120 · confidence: high_ +_Date range: 2023-01-20 to 2023-02-22_ +_Reviewer note: This page (docs/IDE/Project Settings.md) is not in the page index --- it is an IDE guide page, not a Reference page. The finding's package is 'tbIDE' with null symbol; the Export section of Project Settings is the closest fit for this IDE-level gotcha. Verify that the Export section heading anchor is `## Export` (line 138) and that the NOTE sits inside that section. Also confirm the error message text against current IDE behaviour._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> When the Available COM References list shows two entries with identical names and version numbers, this is not a bug. Each entry has a different GUID and corresponds to a distinct type library registered on the system (for example, two installations of the same library from different applications). Selecting each entry in turn and comparing the displayed file path or GUID identifies the specific library to reference. Checking both entries is unnecessary and may cause conflicts. + +_Source threads: 1111079336079020102 · confidence: medium_ +_Date range: 2023-05-25_ +_Reviewer note: Target page is docs/IDE/Project Settings.md (Library References section), which is outside the page-index. The note should be inserted under the '## Library References' heading on that page, near the Available COM References screenshot. Verify the wording accurately describes the IDE's current References dialog behavior -- in particular that selecting a duplicate entry does display a file path or GUID that distinguishes the two libraries._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> When a type library carries the `TYPEFLAG_FCONTROL` flag on one of its interfaces---as the Microsoft Access type library does on `Access.Control`---twinBASIC treats the entire reference as a Components entry (equivalent to an ActiveX control registered via the Components list in VB6). Types from that reference are wrapped in `_ActiveXControlExtender` classes, which implement only the standard COM control interface and are marked non-extensible. Late-bound access to members that are not part of that base interface---such as `ControlSource`, `ControlType`, and other Access-specific control properties---fails with error 5216: `Unrecognized member "..." on type "_ActiveXControlExtender" [non-extensible object]`. +> +> To restore normal late-bound access, open the project's Settings file and set the **Use ActiveX Controls** flag to **False** on the affected reference. After saving, twinBASIC treats the reference as a plain COM type library, and late-bound calls to interface members work as expected. +> +> This is a known limitation. A future release will separate Components and References more precisely, matching VB6 behavior. + +_Source threads: 1114028559451828314 · confidence: high_ +_Date range: 2023-06-02_ +_Reviewer note: Verify the exact name of the 'Use ActiveX Controls' flag as it appears in the .twinproj / Settings JSON, and confirm the error number 5216 is still current. The finding comes from a 2023 Discord thread; the behavior or flag name may have changed. Target page (docs/IDE/Project Settings.md) is not in the Reference page-index --- this is the IDE section. Consider whether the note belongs in the Library References subsection of that page or needs a dedicated new paragraph there._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> When the references list is partially obscured --- for example, when a list item is clipped halfway by the bottom edge of the dialog --- clicking the topmost visible item may select the wrong entry. To avoid this, scroll or resize the dialog so that no item is partially clipped before clicking. This is a known bug; the references and packages lists are planned for an overhaul, at which point the issue is expected to be resolved. + +_Source threads: 1126479798156394516 · confidence: high_ +_Date range: 2023-07-06 to 2023-07-11_ +_Reviewer note: Insertion point is the Library References section of docs/IDE/Project Settings.md. The page is not in the page-index (which covers only docs/Reference/ and a few top-level pages), but was identified by reading the IDE menu pages. Verify that the workaround description still applies to the current IDE version before publishing._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1208711450240090122, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> The icon shown in Windows Explorer and the taskbar for a compiled executable comes from the `Resources\ICON` folder inside the project, not from the **Icon Form** setting. twinBASIC selects the icon file in that folder whose name sorts first alphabetically. To control the application icon, add the desired `.ico` file to the `Resources\ICON` folder and name it so that it sorts before any other icon files in that folder. If the folder is empty, the compiled executable uses a generic icon. + +_Source threads: 1205564031482593380 · confidence: medium_ +_Date range: 2024-02-09 to 2024-02-10_ +_Reviewer note: Verify against current IDE behavior -- the claim is that the icon is taken from Resources\ICON (alphabetically first), not from the Icon Form project property. Needs confirmation against recent IDE release._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1339525725778345997, 1440729215682220074] + +> [!NOTE] +> When a **Package** project type is built, the output filename includes a `_win32` or `_win64` suffix even though a `.twin` package file is architecture-neutral (the consuming project determines the target architecture). This suffix comes from the default build path template, which includes an `_${Architecture}` token. To produce a filename without the suffix, set a custom **Build Output Path** in the project settings that omits the `_${Architecture}` token. + +_Source threads: 1208711450240090122 · confidence: medium_ +_Date range: 2024-02-18_ +_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Build Output Path section of Project Settings. Verify whether the behavior has changed in recent IDE releases._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1440729215682220074] + +> [!NOTE] +> **Import from file** embeds the selected binary (e.g. an `.ocx` file) inside the `.twinproj` file, which can increase the project file size from a few kilobytes to several megabytes. To keep the project file small, use **Add reference** (the available references list) instead --- that stores only a path to the library. When a twinBASIC-native package is available for the library (e.g. `VBCCR18`), prefer the native package: it supports both 32-bit and 64-bit targets and does not embed a binary. **Import from file** is generally not recommended for OCX or DLL files. + +_Source threads: 1339525725778345997 · confidence: high_ +_Date range: 2025-02-13_ +_Reviewer note: Insert under the 'Library References' section of the Project Settings page (after line 34 in the current file). Verify the UI labels ('Import from file' and 'Add reference') match what the IDE actually shows._ + +--- + +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997] + +> [!NOTE] +> When opening a project created with an older version of twinBASIC (before version 9), the IDE may display an "Unable to Load Library" dialog whose title bar shows a GUID rather than a human-readable name. That GUID is the type library ID of the `twinBASIC - WindowsControls DESIGNER Package`, which was a built-in package removed in a later release. To resolve the error: dismiss the dialog, open **Project Settings**, locate the reference entry for that GUID in the **Library References** list, untick it, and save the project. + +_Source threads: 1440729215682220074 · confidence: high_ +_Date range: 2025-11-19_ +_Reviewer note: Page is docs/IDE/Project Settings.md -- not in page-index. Reviewer should confirm placement under the Library References section and verify the package name and tB version number ("before version 9") against the changelog._ + +--- + +## docs/IDE/Toolbar.md · after-remarks + +> [!NOTE] +> When a transient runtime error occurs during execution, restarting the entire IDE is not necessary. The **Restart Compiler** button on the toolbar --- the circular-arrow icon next to the architecture dropdown --- resets the compiler process and usually clears the error state without closing and reopening the IDE. + +_Source threads: 1395281720277991444 · confidence: medium_ +_Date range: 2025-07-18_ +_Reviewer note: Toolbar.md is a sparse list-based page with no prose sections. Verify whether a prose note fits here or whether it belongs in docs/Miscellaneous/FAQs.md instead._ + +--- + +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1336104425520893972, 1118376310725673091] + +> [!NOTE] +> Panel and window layout state is stored independently of project files. A corrupt or incompatible saved layout therefore affects all projects, not only the one that was open when the problem appeared. To recover from a broken layout, delete the saved layout file and restore the default layout from within the IDE. Opening a different project does not reset the layout. + +_Source threads: 1200411286937276508 · confidence: high_ +_Date range: 2024-01-26_ +_Reviewer note: The exact file path for the saved layout is not documented in the thread. Verify the location (likely a per-user app-data file) and consider adding it to the note so users can find it. The IDE index page (docs/IDE/index.md) is not in the page-index --- this is an IDE guide page, not a Reference page._ + +--- + +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1118376310725673091] + +> [!IMPORTANT] +> The twinBASIC IDE uses loopback sockets (127.0.0.1) for all communication between its frontend and backend compiler processes. Any code that installs Windows Filtering Platform (WFP) rules or other network-filter mechanisms must explicitly exempt the loopback address range (127.0.0.0/8) from blocking. A WFP filter that blocks outbound traffic to non-LAN destinations but omits 127.0.0.0/8 from its exclusion list will sever the IDE's internal channels, causing every open twinBASIC instance to enter a crash loop. The fix is to add a filter condition on `FWPM_CONDITION_IP_LOCAL_ADDRESS` excluding 127.0.0.0/8 in addition to any private-range exclusions on `FWPM_CONDITION_IP_REMOTE_ADDRESS`. This loopback architecture is planned to change in a future release. + +_Source threads: 1336104425520893972 · confidence: high_ +_Date range: 2025-02-03 to 2025-02-05_ +_Reviewer note: The finding was tagged package=Core, symbol=null, which maps to UNMAPPED in the page index. Placed here on docs/IDE/index.md as this is an IDE-level architectural warning. Verify that the IDE index page is the appropriate home, or consider a dedicated troubleshooting page if one is added in future._ + +--- + +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1336104425520893972] + +> [!NOTE] +> The IDE stores all its settings in the Windows registry under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE`. Because settings are stored in the registry rather than in the installation folder, installing a new release to a fresh directory preserves existing settings automatically. To reset the IDE to its defaults, delete or rename this registry key. + +_Source threads: 1118376310725673091 · confidence: high_ +_Date range: 2023-06-14_ +_Reviewer note: The IDE section pages (docs/IDE/) are not in the page-index. This addition targets docs/IDE/index.md as the general IDE overview page. Reviewer should confirm placement --- the Project Settings page (docs/IDE/Project Settings.md) covers per-project settings, not IDE-level settings, so the index page is the better fit._ + +--- + +## docs/IDE/index.md · new-section + +## Requirements + +### Microsoft Edge WebView2 Runtime + +The twinBASIC IDE requires the Microsoft Edge WebView2 Runtime to be installed on the development machine. Without it, opening certain project types (such as Standard EXE) can produce an "Unable to Load Library" error and a non-functional designer. + +Download the x86 (32-bit) Evergreen Standalone Installer from the [Microsoft Edge WebView2 download page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) and run it on the development machine. + +> [!NOTE] +> This is a development machine requirement only. Executables compiled with twinBASIC do not require the WebView2 Runtime on end-user machines. + +### Extracting the distribution ZIP + +Before extracting the twinBASIC distribution ZIP, remove the Windows zone identifier ("Mark of the Web") from the file: right-click the ZIP in File Explorer, open **Properties**, and click **Unblock** if the button is present. Extracting without doing this can cause the IDE to treat the extracted files as untrusted, resulting in library-load failures. + +Extract to a conventional location such as a subfolder of Downloads, Documents, or a dedicated tools directory. Extracting directly into a special shell folder (for example the user profile root `C:\Users\`) can produce a broken installation in which packages such as WinNativeFormsBase fail to load, even when the file counts appear correct. + +_Source threads: 1111043339987468380 · confidence: medium_ +_Date range: 2023-05-24 to 2023-05-26_ +_Reviewer note: Target page docs/IDE/index.md is not in the page-index (package 'Core', symbol null has no page-index match). Placement here is a best-effort choice -- the IDE index page is the closest existing page covering the IDE as a whole. A dedicated installation/setup page may be a better long-term home. The WebView2 Runtime finding (high confidence, 2023-05-24--25) and the two ZIP-extraction findings (medium confidence, 2023-05-25--26) are merged into one section. Verify: (1) that the IDE still requires the 32-bit Evergreen installer rather than the Evergreen bootstrapper or a fixed-version installer; (2) whether the user-profile-root extraction issue still reproduces on current builds; (3) whether 'Unblock' behavior differs between Windows 10 and 11._ + +--- + +## docs/IDE/tbForm.md · after-remarks + +> [!NOTE] +> If the form designer fails to finish loading and stays at a fixed percentage (for example, 15%) without progressing, the most likely cause is that the IDE toolbar panel has been closed. Go to **Window** > **Panel Layouts** > **Default Built-in Layout** to restore the default panel arrangement, then restart the IDE. An anti-virus or firewall blocking the WebSocket connection between the IDE and the compiler can also prevent the designer from loading. This issue was resolved in BETA 449. + +_Source threads: 1205932211954786425 · confidence: high_ +_Date range: 2024-02-10 to 2024-02-11_ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] + +
+I get "Internal Error 33330: IDE was unable to read builtin package file data" when adding a package reference. + +This error occurs when twinBASIC is extracted on top of a previous installation rather than into a clean folder. The IDE cannot read its built-in package data if stale files from an older release are present alongside the new ones. + +To resolve it, extract the new release into an empty folder. Delete the contents of the old installation folder first, or use a different folder entirely. See [How do I install twinBASIC](#installation) for the recommended extraction steps. + +The error affects all built-in package references and `.twinpack` files, not only the one that was being added when it first appeared. + +
+ +_Source threads: 1241295009110687764 · confidence: high_ +_Date range: 2024-05-18_ +_Reviewer note: This finding maps to the Installation section of docs/Miscellaneous/FAQs.md (not in the page-index). Insert the new
block after the existing #installation entry (around line 188) and before #installation-size. The existing installation entry already mentions extracting to an empty folder but only vaguely; this new entry gives the specific error number (33330) and symptom (package load failure) so users can find it by searching. Confirmed by WaynePhillipsEA in the Discord thread._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] + +Installing the specific updates KB4490628 and KB4516065 (with a reboot between them) has been reported to resolve this requirement on Windows 7 SP1. After applying all available updates, the entry-point error should no longer appear; if WebView2 is absent or at the wrong version for Windows 7, the next error on launch will be about WebView2 instead --- see [How do I install WebView2 on Windows 7 or Windows 8.1?](#webview2-windows7) for the correct procedure. + +_Source threads: 1332299294006448170 · confidence: high_ +_Date range: 2025-01-24 to 2025-01-26_ +_Reviewer note: This text should be appended to the body of the existing 'invalid-entry-point' FAQ entry (around line 159-163 of FAQs.md), which currently ends after advising the user to run Windows Update. The KB numbers (KB4490628, KB4516065) came from a community report and should be verified before publication._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] + +After importing, the recommended first step is to save the project in the same directory as the original VBP so that relative paths to referenced files remain intact, then run or debug the project to identify any remaining issues. + +_Source threads: 1083431012417155154 · confidence: medium_ +_Date range: 2023-05-27_ +_Reviewer note: This workflow tip belongs in the 'How do I import my VB6 project into twinBASIC?' FAQ entry, appended after the existing content. The surrounding FAQ entry already notes that resource files are imported automatically when selecting the .vbp; this sentence adds the save-in-place step. Verify that the "save in same directory" advice is still current best practice._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] + +> [!NOTE] +> To convert an imported VB6 project into a twinBASIC package, open the `.vbp` file in twinBASIC as usual, then open the project Settings and change the project type from **Standard EXE** to **Package**. After saving the settings the compiler restarts and the Package Manager pane becomes visible in the IDE. + +_Source threads: 1180541541945712700 · confidence: high_ +_Date range: 2023-12-02_ +_Reviewer note: Insert near the existing `#vb6-import` FAQ entry. Confirmed by WaynePhillipsEA in the thread._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1330341380496560128, 1331867785944371301, 1396409493981626409] + +> [!NOTE] +> Windows 95 and Windows NT 4.0 are not supported targets. The runtime startup routine calls APIs absent from those platforms---for example, `GetVolumePathNameW` is not part of the Windows 95 / NT 4.0 kernel32---so produced binaries will fail to launch. The minimum version currently confirmed to work is Windows XP; the intended minimum target is Windows 2000. + +_Source threads: 1181674508449480815 · confidence: high_ +_Date range: 2023-12-05 to 2023-12-08_ +_Reviewer note: Insert into or alongside the existing `#runtime-requirements` FAQ entry, which already states 'minimum supported Windows version is Windows XP'. The WinMain entry-point override (for bypassing the startup routine) was described in the thread as unsupported for general use and intended only for kernel-mode driver targets --- omitted here to avoid encouraging an unsupported path. Verify that Windows 2000 status has not changed since the thread date._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1331867785944371301, 1396409493981626409] + +> [!NOTE] +> Individual `.frm` form files cannot be added to an existing twinBASIC project after the initial import. To incorporate additional VB6 forms, start a new project that includes all required forms from the beginning. `.bas` module files and `.cls` class files can be imported individually into an existing project at any time. + +_Source threads: 1330341380496560128 · confidence: medium_ +_Date range: 2025-01-19_ +_Reviewer note: This note should be added inside the `vb6-import` `
` block in the FAQ, after the existing NOTE callout about .bas/.cls import. Verify that the one-way form conversion restriction still applies in current builds._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1396409493981626409] + +> [!NOTE] +> Windows XP is not supported. The twinBASIC IDE requires Windows 7 or later because the IDE depends on WebView2, which does not run on Windows XP. Both 32-bit and 64-bit editions of Windows 7 are supported (fully updated). See the entry above for the update requirement on Windows 7. + +_Source threads: 1331867785944371301 · confidence: high_ +_Date range: 2025-01-23_ +_Reviewer note: Insert inside or directly after the `system-requirements` `
` block, as a clarification that Windows XP is out of scope. The block already states 'Windows 7 through Windows 11' but does not explicitly call out XP. Note that the `runtime-requirements` entry separately states that *compiled EXEs* support Windows XP as a minimum --- this addition is only about the IDE._ + +--- + +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301] + +> [!NOTE] +> twinBASIC produces native machine code comparable to a C++ or VB6 native-compilation build. Tools designed to decompile VB6 p-code or .NET IL --- such as VB6 Decompiler or .NET reverse-engineering utilities --- cannot be applied to twinBASIC executables or DLLs. Analyzing a twinBASIC binary requires native-code tools such as x64dbg or IDA. + +_Source threads: 1396409493981626409 · confidence: high_ +_Date range: 2025-07-20 to 2025-07-27_ +_Reviewer note: FAQs.md is not in the page-index. The note fits best in the 'Using twinBASIC' section, potentially as a new FAQ entry about reverse engineering / decompilation. Reviewer should decide whether to add a new
entry or fold this into the existing backwards-compatibility FAQ._ + +--- + +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1202802099356966962, 1332299294006448170, 1447957791770677382] + +
+The IDE reports "IDE was unable to read builtin package file data" (Internal Error 33330). + +This error occurs when a new twinBASIC ZIP is extracted into a folder that already contains an older installation. A slight change in the folder structure between releases means the mixed old and new files cause the error. + +To fix it: delete the old twinBASIC folder completely, then extract the new ZIP into a fresh, empty directory. If the error persists after a clean extraction, check whether your antivirus software has quarantined any of the twinBASIC files --- see [The IDE reports missing files](#missing-files) for steps to resolve that. + +
+ +_Source threads: 1169313912823496866 · confidence: high_ +_Date range: 2023-11-01 to 2023-11-10_ +_Reviewer note: This entry belongs in the Installation section of FAQs.md, ideally placed after the existing 'missing-files' entry (around line 177) since it references that entry. The existing 'installation' FAQ (line 180) already advises extracting to an empty folder but does not describe this specific error code. Confirm the exact placement within the Installation section._ + +--- + +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1332299294006448170, 1447957791770677382] + +
+My VB6 import stalls partway through on a large project. + +When converting a large VB6 project, the import progress bar may appear to complete, the Project Explorer fills briefly, then clears---after which a second progress bar appears but never advances. The IDE remains open but unresponsive. This is a symptom of the compiler process running out of virtual address space. + +On 64-bit Windows, a standard Win32 process has a 2 GB virtual address space limit. Known memory leaks in the compiler (present during the BETA period) can push memory use above 1.8 GB for large projects, which is enough to cause a crash at that limit. + +The workaround is to enable the **Large Address Aware (LAA)** flag, which raises the virtual address limit to 4 GB for a 32-bit process running on 64-bit Windows. Enable it in Project Settings before retrying the import. + +> [!NOTE] +> This workaround addresses the symptom. The underlying memory leaks are a known issue being resolved in later releases. Once the leaks are fixed, LAA will no longer be required for import. + +
+ +_Source threads: 1202802099356966962 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-07_ +_Reviewer note: Insertion point: after the closing
of the 'vb6-import' entry (line 246 in the current file) and before the 'unrecognized-variables' entry. The LAA setting referenced here appears in docs/IDE/Project Settings.md at the 'Large Address Aware (LAA)' heading. Verify the current compiler memory situation -- the thread is from February 2024 (BETA) and the leaks may have been partially addressed since then; if so, soften the NOTE or remove it._ + +--- + +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1447957791770677382] + +
+How do I install WebView2 on Windows 7 or Windows 8.1? + +Microsoft dropped Windows 7 and Windows 8.1 support from the WebView2 evergreen installer after version 109. Running any newer evergreen installer on those systems produces an "installer failed to start" error. Version 109 (109.0.1518.140) is the last release to support these operating systems and must be installed instead. + +Because version 109 is no longer distributed as a standalone download, the procedure to install it is: + +1. Open the [Microsoft Update Catalog](https://www.catalog.update.microsoft.com/Search.aspx?q=webview2%20109) and search for **webview2 109**. +2. Download the package for your architecture. +3. The downloaded file cannot be run directly --- it must first be extracted using a tool such as 7-Zip. +4. After extraction, locate the file named `MicrosoftEdge_X64_109.0.1518.140.exe.{GUID}` (the exact GUID varies). Rename it by removing the `.{GUID}` suffix so that the name ends in `.exe`. +5. Running the renamed executable without arguments installs the full Edge browser. To install only the WebView2 runtime that twinBASIC requires, pass the following flags: + + ``` + MicrosoftEdge_X64_109.0.1518.140.exe --msedgewebview --system-level + ``` + +After the runtime installs, relaunch the twinBASIC IDE. If the IDE still reports a missing WebView2 installation, verify that the correct architecture was downloaded and that the `--system-level` flag was included. + +
+ +_Source threads: 1332299294006448170 · confidence: high_ +_Date range: 2025-01-26 to 2025-03-07_ +_Reviewer note: This new FAQ entry should be placed in the Installation section, immediately after the existing 'system-requirements' entry (around line 156 of FAQs.md). The x64 filename in the extract step should be verified against the actual catalog download --- the 32-bit variant uses the x86 filename instead._ + +--- + +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1332299294006448170] + +
+Double-clicking a .twinproj file in Explorer does not open twinBASIC. + +Each time `twinbasic.exe` is launched, it registers itself in the Windows registry as the application associated with `.twinproj` files. Because twinBASIC has no installer, if the IDE executable is deleted or moved (for example when removing an older BETA release) before the replacement version has been launched at least once, the `.twinproj` file association points to a non-existent path. Double-clicking a `.twinproj` file in Explorer will then fail to open twinBASIC. + +The fix is to launch the new `twinbasic.exe` directly from its folder before removing the old version, so that the registry entry is updated to the new path. + +
+ +_Source threads: 1447957791770677382 · confidence: high_ +_Date range: 2025-12-09 to 2025-12-10_ +_Reviewer note: This entry belongs in the Installation section of the FAQs page, after the existing entries for missing files and the installation procedure. The page uses
/ HTML elements; match that formatting exactly._ + +--- + +## docs/Reference/Assert/index.md · after-remarks + +> [!NOTE] +> When a subroutine is annotated with `[TestCase]`, the twinBASIC IDE hides the inline **Run** action that normally appears above the `Sub` declaration. To run a parameterised test interactively, create a wrapper subroutine without `[TestCase]` that calls the test with the desired arguments. + +_Source threads: 1194682995538407434 · confidence: medium_ +_Date range: 2024-01-10_ +_Reviewer note: Verify the [TestCase] inline-run behaviour against the current IDE release; this was observed in early 2024 and may have changed. Also check whether a Test Explorer panel has been added to the IDE since then, as the finding notes none existed at that time._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> twinBASIC enables integer overflow checks by default (`True`), which differs from compiled VB6 executables. VB6 raises overflow errors only when running in the IDE; compiled VB6 executables do not check for integer overflow at runtime. Apply `[IntegerOverflowChecks(False)]` to a function to suppress overflow checking for that function and match compiled VB6 behavior. + +_Source threads: 1120535095279886416 · confidence: high_ +_Date range: 2023-10-22_ +_Reviewer note: This addition targets the existing `IntegerOverflowChecks` section (line 413) in docs/Reference/Attributes.md. The note should be inserted after the one-line description "Disables integer overflow checks. Used on performance-critical routines. The default value is **True**." Verify that the VB6 compiled-mode behavior is still accurate for current twinBASIC releases._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The `[Description()]` value must be a single-line string. If the value contains newline characters (for example, produced by concatenating `vbCrLf` into the string), the IDE accepts the declaration without a compile error, but hovering over the member or invoking autocomplete on it produces a 'bad JSON received' error. The LSP response embeds the description text directly inside a JSON string, and a literal newline in that string breaks JSON parsing. Keep `[Description()]` values to a single line with no embedded newline characters. + +_Source threads: 1172641655631908884 · confidence: medium_ +_Date range: 2023-11-10_ +_Reviewer note: Verify whether this limitation has been fixed in recent IDE builds. The thread is from November 2023 and the behaviour may have changed. Anchor this note directly after the Description attribute's prose (after the 'helpstring attribute in the type library' sentence, before the next ## heading)._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> When typing `[CoClassId(...)]`, IntelliSense offers to auto-generate a random GUID --- the same prompt available for `[InterfaceId(...)]`. This behavior was introduced in BETA 438; earlier versions required the GUID to be generated and pasted manually. + +_Source threads: 1202939463630594079 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-06_ +_Reviewer note: Insert this note within the `## CoClassId` section of Attributes.md, after the existing syntax/applicability description. Verify against the current IDE build that the GUID auto-generation prompt is still present for both [CoClassId] and [InterfaceId]._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> `[IgnoreWarnings]` applies only to diagnostic codes in the **TB0000--TB4999** range (warnings). Compilation errors are all reported under the single code **TB5000** and cannot be suppressed. Unique error IDs in the TB5000+ range are planned for a future release to allow associated help pages and easier searching. + +_Source threads: 1212815197501395054 · confidence: high_ +_Date range: 2024-02-29_ +_Reviewer note: Insert this NOTE immediately after the IgnoreWarnings syntax line (line 411 in Attributes.md), before the next section heading. Verify current status of unique TB5000+ IDs against the latest twinBASIC build notes._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> `[IntegerOverflowChecks(False)]` does not suppress overflow errors when the overflowing expression contains a division operation (e.g. `(6 + 52 / 4) * &H9E3779B9`). Division produces a `Double` intermediate, so the overflow is detected by the floating-point unit rather than the integer overflow checker. Apply `[FloatingPointErrorChecks(False)]` alongside `[IntegerOverflowChecks(False)]` to suppress it. Before BETA 473, `[FloatingPointErrorChecks(False)]` had no effect when all operands were compile-time constants; moving at least one operand into a variable was the workaround. This constant-folding limitation was resolved in BETA 473. + +_Source threads: 1218599950359855186 · confidence: high_ +_Date range: 2024-03-16 to 2024-03-18_ +_Reviewer note: Insert immediately after the existing `IntegerOverflowChecks` description paragraph (after line 420 of Attributes.md). Verify the BETA 473 version number against the release notes._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Applying `[IntegerOverflowChecks(False), FloatingPointErrorChecks(False)]` to a procedure does not necessarily prevent overflow or floating-point errors from firing at runtime inside the IDE. However, when both attributes are present, the IDE's **Resume Next** button in the error popup works correctly and execution continues normally. Without them, the IDE can stop responding on the error popup, requiring the process to be terminated externally. The source of such an overflow may be a code generation issue in a neighboring line rather than the line the error reports, making isolation by reproducing the values alone unreliable. (The underlying FPU code generation edge case was fixed in BETA 520.) + +_Source threads: 1230934350959345735 · confidence: high_ +_Date range: 2024-04-20 to 2024-04-24_ +_Reviewer note: Insert this note after the existing one-line description under the `IntegerOverflowChecks` section (line 420 in Attributes.md), before the next `##` heading. The note refers to both IntegerOverflowChecks and FloatingPointErrorChecks used together._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The description text is rendered as markdown in the IDE's IntelliSense tooltip. Embedding `vbCrLf` in the string literal does not produce a visible line break. To force a line break, end the line with a trailing backslash before the string concatenation, for example: +> +> ```tb +> [Description("Line 1\" & vbCrLf & _ +> "Line 2")] +> ``` + +_Source threads: 1252891072774803487 · confidence: high_ +_Date range: 2024-06-19 to 2024-06-22_ +_Reviewer note: Verify the exact syntax for line breaks in Description attribute strings. The example in the detail uses a backslash before the closing quote of each segment; confirm this is correct twinBASIC syntax and that the trailing-backslash trick actually works as described. This note belongs under the Description attribute entry (the #description anchor), not at the top of the file._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The placement of `[TypeHint]` differs depending on what is being annotated. For a **parameter**, place the attribute on the line immediately before the parameter declaration inside the procedure signature. For a **Property Get or Function return value**, place the attribute on the line immediately before the entire procedure declaration --- not inline with the return type. Example: +> +> ```tb +> ' TypeHint on a return value: attribute goes above the procedure +> [TypeHint(ScaleModeConstants)] +> Property Get ScaleMode() As Integer +> ScaleMode = m_ScaleMode +> End Property +> +> ' TypeHint on a parameter: attribute goes above the parameter +> Property Let ScaleMode( +> [TypeHint(ScaleModeConstants)] ByVal Value As Integer) +> m_ScaleMode = Value +> End Property +> ``` + +_Source threads: 1333587859474747413 · confidence: high_ +_Date range: 2025-02-03_ +_Reviewer note: This addition should be inserted into the TypeHint section (around line 633-640) of docs/Reference/Attributes.md, after the 'Applicable to:' line. Verify the code example compiles correctly in twinBASIC --- the thread confirms the behaviour but the exact syntax should be checked against the Attributes page style._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Applying `[OleAutomation(False)]` to an `Interface` declaration causes `RegisterTypeLib()` to skip registering that interface in the Windows registry. This only affects ActiveX DLL output that is registered; in a Standard EXE project the attribute has no observable effect. twinBASIC defaults interfaces to `[OleAutomation(True)]`, which differs from traditional MIDL/MKTYPLIB compilers that default to `False`. When defining COM interfaces intended for vtable binding only (not via `IDispatch`), `[OleAutomation(False)]` keeps the type library entry without registering the interface. + +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-06-05_ +_Reviewer note: Insert after the existing OleAutomation section body (line ~461 in Attributes.md). The section currently states only that the attribute 'Controls whether this attribute is applied in the typelibrary' and 'is set to True by default' -- this note adds the RegisterTypeLib and ActiveX DLL context._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Within a `[RunAfterBuild]` subroutine, `App.LastBuildPath` returns the full path to the just-produced executable or DLL. This makes it straightforward to automate post-build tasks such as code signing or file copying. + +_Source threads: 1079642242429489262 · confidence: high_ +_Date range: 2023-02-27_ +_Reviewer note: Insert immediately after the existing RunAfterBuild section description (currently lines 582-589 of docs/Reference/Attributes.md). The existing text ends with the `App.LastBuildPath` mention but is informal and contains a typo ('Tthere's'); this NOTE supplements the corrected prose that should replace it._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Support for `[Description]` on **Const** declarations was added in BETA 268. Prior builds accepted the syntax but the description did not appear in IntelliSense tooltips. + +_Source threads: 1082767362253668474 · confidence: high_ +_Date range: 2023-03-07 to 2023-03-12_ +_Reviewer note: Insert inside the existing Description section (currently lines 263-270 of docs/Reference/Attributes.md), after the applicability line that lists Const. Verify the BETA 268 version number against release notes if available._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The twinBASIC default for `[OleAutomation]` is **True**, which is the opposite of the MIDL convention where `[oleautomation]` defaults to absent (equivalent to **False**). When importing a type library, interfaces that should not be OLE Automation compatible must be marked `[OleAutomation(False)]` explicitly, because omitting the attribute leaves them as **True** in twinBASIC. + +_Source threads: 1083119492789448824 · confidence: high_ +_Date range: 2023-03-08 to 2023-03-12_ +_Reviewer note: Insert inside the existing OleAutomation section (currently lines 454-462 of docs/Reference/Attributes.md), after the existing 'This attribute is set to True by default' sentence._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +The argument accepts a comma-separated list of enum type names to merge members from multiple enumerations into a single IntelliSense completion list. This is useful when a single numeric parameter draws values from two logically distinct enumerations that should not be merged into one physical **Enum**. + +```tb +[TypeHint("SomeEnum, OtherEnum")] +Public Sub SetFlags(ByVal flags As Long) +``` + +IntelliSense will then show members from both `SomeEnum` and `OtherEnum` as candidates for the `flags` argument. + +_Source threads: 1103494252811526164 · confidence: high_ +_Date range: 2023-05-04 to 2023-05-12_ +_Reviewer note: Insert after the existing one-line description of TypeHint in the Attributes page (the current entry only says 'Allows populating Intellisense with an enum for types other than Long'). The multi-enum syntax was added in BETA 298._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> When the GUID string is removed from a `[ClassId("...")]` attribute (leaving the attribute empty or absent), the IDE displays a CodeLens entry directly above the attribute. Clicking it inserts a freshly generated GUID using `CoCreateGuid()`. This feature is available from BETA 323. The CodeLens appears only after the GUID value is removed; leaving an empty string `("")` in place may not trigger it. + +_Source threads: 1113738858408988713 · confidence: high_ +_Date range: 2023-06-01 to 2023-06-02_ +_Reviewer note: Ideally place this note adjacent to the ClassId section in Attributes.md rather than as a free-standing after-remarks block; the reviewer should decide exact placement within the file._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The `[Description]` value is stored as a COM typelib `helpstring`. The underlying limit (from MIDL error 2113/2114) is 255 characters; twinBASIC's effective limit is approximately 235 characters. Exceeding this length raises an "internal error: too long" message. Keep `[Description]` text within 230 characters to stay clear of the limit. + +_Source threads: 1135126325783449711 · confidence: medium_ +_Date range: 2023-07-30_ +_Reviewer note: Verify the exact character limit against the current compiler behavior; the 235-character figure is approximate and based on a single report._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Multiple enums can be combined in a single `[TypeHint]` by listing them separated by commas: `[TypeHint(EnumA, EnumB)]`. This causes IntelliSense to offer members from all listed enums for that parameter. The attribute works on `Declare` function parameters and `Sub`/`Function` parameters; UDT members are not currently supported. + +_Source threads: 1146837274357678203 · confidence: high_ +_Date range: 2023-08-31_ +_Reviewer note: Verify the multi-enum comma syntax and the UDT member exclusion against the current compiler source._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> **`[DllExport]` on constants and executables.** The attribute applies to `Const` declarations as well as to procedures and variables. A practical use is exporting a `DWORD` constant named `NvOptimusEnablement` with a value of `1` from an x64 executable, which signals the GPU driver to activate the dedicated GPU for that process --- a requirement some GPU configurations have no reliable pure-code workaround for. `[DllExport]` also functions on executables, not only on DLLs. + +```tb +[DllExport] +Public Const NvOptimusEnablement As Long = 1 +``` + +_Source threads: 1165150931554418698 · confidence: high_ +_Date range: 2023-10-21_ +_Reviewer note: Insert after the existing DllExport section (after the code block on line 304 and before the DLLStackCheck heading). Verify that the NvOptimusEnablement pattern actually requires DllExport from an EXE and is not a DLL-only requirement._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +The attribute is used throughout the built-in packages to mark members that are declared in the type definition but have no working implementation yet. Code that references such a member compiles without an "unrecognized member" error, and the IDE surface (IntelliSense, type-library viewer) shows the member as present, but calling it at runtime produces no result or raises an error. The [**Printer**](../VB/Printer/) class uses this pattern for several of its members. + +_Source threads: 1167815153253892156 · confidence: high_ +_Date range: 2023-10-28_ +_Reviewer note: Insert as additional prose after the existing two-line Unimplemented section body. The Printer page already documents RightToLeft as not implemented; verify that other Printer members (Width, Height, FontName, hDC, CurrentX) are truly flagged [Unimplemented] in the .twin source rather than fully implemented._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +The string is rendered as markdown in the IDE's IntelliSense popup and hover tooltip (the Monaco editor has built-in markdown support). Multi-line descriptions are built by concatenating fragments with `& vbCrLf &`; a markdown line break requires two or more trailing spaces before the line-feed constant. Only `vbCrLf` is supported for line endings inside attribute strings. Markdown headers (`###`), inline code (backtick), fenced code blocks (triple backtick with a language tag), bold, and italic are all supported. Emoji characters embedded in the string render correctly in the twinBASIC IDE but may display as garbled text in non-twinBASIC COM consumers whose type-library readers do not handle surrogate pairs. + +```tb +[Description("Returns the widget count. " & vbCrLf & _ + "### Notes" & vbCrLf & _ + "`Count` is zero-based.")] +Public Property Get Count() As Long +``` + +_Source threads: 1169508594341916712 · confidence: high_ +_Date range: 2023-11-02 to 2023-11-06_ +_Reviewer note: Insert as additional prose after the existing Description section body (after line 271). The current text ends after 'helpstring attribute in the type library (if applicable).' Append the new paragraph before the next heading._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> **`[EnumId]` on UDTs.** The attribute can also be applied to a [**Type** (UDT)](Type) definition, not only to an **Enum**. Assigning a GUID to a UDT allows it to be passed as a parameter across type-library boundaries --- without a GUID, passing a UDT defined in one type library as a parameter in a component from a different type library raises a compile error. The trade-off is that the type library then behaves like an ActiveX control for versioning: replacing it with a new build requires unregistering the old version first, overwriting the file, and re-registering. Simply overwriting the file (which works for plain TLBs) is no longer safe once a GUID has been assigned to any UDT in the library. + +_Source threads: 1173257226610290699 · confidence: high_ +_Date range: 2023-11-12 to 2023-11-14_ +_Reviewer note: Insert after the existing EnumId body (after line 348, before the EventInterfaceId heading). Also update the 'Applicable to' line from '[Enum]' to '[Enum], [Type (UDT)](Type)' if the .twin source confirms this._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Although `[Unimplemented]` was not originally designed for general use, it is available in user code. A common pattern is to apply it to interface methods that must be declared to satisfy an interface contract but are intentionally left without an implementation body. Unlike `#If`/`#End If` conditional compilation, which removes code from the compilation entirely, `[Unimplemented]` keeps the method in the compiled output and type-checked --- so the compiler still catches downstream breakage if other code changes --- while issuing a warning at every call site. + +_Source threads: 1179991719551442975 · confidence: medium_ +_Date range: 2023-12-01 to 2023-12-03_ +_Reviewer note: Verify that [Unimplemented] is indeed documented as user-accessible in the current .twin sources; the finding notes it was 'not originally intended for general public use'. Placement: append this note immediately after the existing [Unimplemented] section body in Attributes.md._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> `Debug.Assert` is evaluated only when running inside the IDE debugger. All `Debug.Assert` calls are removed from compiled binaries and their conditions are never evaluated at runtime. For runtime assertion checks in compiled builds, use the [Assert package](../Assert/) or write an explicit error-raising guard. A project-level option to retain `Debug.Assert` in compiled binaries was under discussion as of early 2024 but was not yet implemented. + +_Source threads: 1206670811172306954 · confidence: high_ +_Date range: 2024-02-12_ +_Reviewer note: Placement: append immediately after the existing [DebugOnly] section body in Attributes.md, since that section already references Debug.Assert and Debug.Print. Alternatively, consider a dedicated Note block under the Assert package index page._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> A `[DefaultMember]` property that takes no parameters is evaluated automatically whenever an instance of the class appears in an expression, including when the instance is passed as an argument. This matches COM/VBA semantics and is intentional for compatibility with non-twinBASIC COM consumers. There is no per-parameter annotation to suppress this automatic evaluation. If automatic evaluation is not desired, the default member must accept at least one parameter, or the `[DefaultMember]` attribute must be omitted entirely. + +_Source threads: 1358081642052190318 · confidence: high_ +_Date range: 2025-04-05 to 2025-04-06_ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> When `[DebugOnly]` is applied to a Sub or Function, the compiler removes both the procedure body and every call site that references it from the compiled EXE. Only the IDE (debug) run includes the procedure. This is distinct from `[Debuggable(False)]`, which suppresses breakpoints and stepping but does not strip code from the build. + +_Source threads: 1398715673508843731 · confidence: high_ +_Date range: 2025-07-26_ +_Reviewer note: Insert immediately after the existing DebugOnly entry body (after line 231 in Attributes.md, under the ## DebugOnly section). The current description says 'Excludes calls to this procedure from the Build' --- this NOTE adds that the procedure itself is also excluded, not just the calls._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The string value in `[Description("...")]` is rendered as markdown in the IDE tooltip, not as plain text. Embedding `vbCrLf` in the string does not produce visible line breaks. To produce multi-line or formatted IntelliSense tooltips, use markdown syntax within the string. Built-in functions use this pattern --- selecting 'Go to definition' on any built-in function in the IDE shows examples of how these strings are authored. + +_Source threads: 1396063342417674291 · confidence: high_ +_Date range: 2025-07-19_ +_Reviewer note: Insert immediately after the existing Description entry body (under ## Description in Attributes.md). The current entry says 'Provides a description in information popups in the IDE' without mentioning markdown rendering._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> +> Square-bracket attributes apply to the next syntax element the parser encounters. Standard modules (`.BAS` files) have no explicit `Module` block --- the module container is implicit --- so there is no syntax element for an attribute to attach to before the first declaration. As a result, module-scoped settings that would be expressed as attributes in a Class or Form module must instead be expressed as `Option` statements (for example, `Option PtrSafe Off`) in a standard module. This limitation may change if the parser is updated to support attributes at the start of a module file before any declarations. + +_Source threads: 1421815804172701708 · confidence: high_ +_Date range: 2025-10-01_ +_Reviewer note: Confirmed by WaynePhillipsEA. Verify that 'Option PtrSafe Off' is the correct syntax for the relevant option statement and that the Option page documents it, before adding this cross-reference. Also consider whether this note belongs on the Option page instead of (or in addition to) the Attributes page._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Under the **ArrayBoundsChecks** section, after the existing description, insert: + +> [!NOTE] +> **[ArrayBoundsChecks(False)]** is not fully implemented in the current compiler. Suppression works on array write operations but may not suppress bounds checks on reads in all contexts --- for example, reading a known out-of-bounds element through `MsgBox` still raises a bounds error even when the attribute is present, whereas reading through `Debug.Print` succeeds. Do not rely on the attribute to eliminate all bounds-checking overhead until this is resolved. + +> [!NOTE] +> With the current unoptimized code generator, **[ArrayBoundsChecks(False)]** may produce no measurable performance improvement. The bounds-checking code may still be emitted and simply not reached at runtime. Meaningful gains from disabling bounds checks require LLVM optimization, which is experimental and limited to procedures whose argument and variable types are numeric scalars only (no strings, objects, or dynamic arrays). Apply **[CompilerOptions("+llvm,+optimize")]** alongside this attribute to obtain the intended speedup. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-11-28_ +_Reviewer note: Two separate limitations confirmed by the twinBASIC maintainer (WaynePhillipsEA). The read-vs-write asymmetry is a known bug; the LLVM dependency is by design. Verify both are still current before publishing._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Under the **UseGetLastError** section, after the existing description, insert: + +> [!NOTE] +> Every `Declare` statement implicitly calls `GetLastError` after the API returns to populate `Err.LastDllError`. This hidden call adds overhead that is noticeable in tight loops. For APIs that do not use `GetLastError` --- for example, string comparison functions or functions returning NTSTATUS --- add **[UseGetLastError(False)]** to suppress the implicit call: +> +> ```tb +> [UseGetLastError(False)] +> Public Declare PtrSafe Function CompareStringEx Lib "kernel32" ( _ +> ByVal lpLocaleName As LongPtr, ByVal dwCmpFlags As Long, _ +> ByVal lpString1 As LongPtr, ByVal cchCount1 As Long, _ +> ByVal lpString2 As LongPtr, ByVal cchCount2 As Long, _ +> ByVal lpVersionInformation As LongPtr, ByVal lpReserved As LongPtr, _ +> ByVal lParam As LongPtr) As Long +> ``` +> +> When working with APIs from a package reference that cannot be edited directly, copy the `Declare` into the project and add the attribute there. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-12-06_ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> A class's predeclared instance is not a compile-time constant. Because **Optional** parameter default values must resolve to a constant, writing `Optional ByVal Target As MyClass = MyClass` (using the predeclared instance as the default) is not valid. The standard workaround is to declare the parameter as `Optional ByVal Target As MyClass = Nothing` and test inside the procedure: +> +> ```tb +> Sub DoSomething(Optional ByVal Target As MyClass = Nothing) +> If Target Is Nothing Then Set Target = MyClass +> ' ... +> End Sub +> ``` + +_Source threads: 1291780674772275220 · confidence: medium_ +_Date range: 2024-10-04_ +_Reviewer note: This note should appear in the PredeclaredID section of Attributes.md, immediately after the existing two-sentence description. Confirm placement does not conflict with any existing notes in that section._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Applying `[Debuggable(False)]` to a procedure or module causes the debugger to skip over that code when stepping through with F8. This is useful when a referenced package contains source code that would otherwise be entered during step-through. If the source belongs to a read-only package that cannot be edited from within the consuming project, the package author must set this attribute in their own project before building the package. + +_Source threads: 1357558567447695391 · confidence: high_ +_Date range: 2025-04-04 to 2025-04-07_ +_Reviewer note: Insert after the existing [Debuggable] attribute entry (around the 'When false, turns of breakpoints and stepping' line). The note about a per-reference consumer-side override switch (planned but not yet available as of April 2025) was omitted as it may become stale quickly; re-add when the feature ships._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> Both `[ArrayBoundsChecks]` and `[IntegerOverflowChecks]` can be applied to a single procedure as well as to an entire class or module. Prefixing one performance-critical function disables the corresponding runtime check for that function only: +> +> ```tb +> [ArrayBoundsChecks(False), IntegerOverflowChecks(False)] +> Private Sub CriticalInnerLoop(arr() As Long, ByVal n As Long) +> Dim i As Long +> For i = 0 To n - 1 +> arr(i) = arr(i) + 1 +> Next i +> End Sub +> ``` + +_Source threads: 1382015106380075078 · confidence: medium_ +_Date range: 2025-06-11_ +_Reviewer note: This note should be inserted under both the ArrayBoundsChecks and IntegerOverflowChecks sections (or added once after IntegerOverflowChecks with a cross-reference). The existing Attributes.md text already states both attributes are applicable to procedure scope, so this is a clarifying example rather than new information. Place it under the IntegerOverflowChecks section._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +## NonBrowsable (optional Bool) +{: #nonbrowsable } + +Syntax: **[NonBrowsable** [ **(** **True** \| **False** **)** ] **]** + +Applicable to: [**Property**](Property) procedures (**Property Let**, **Property Get**, **Property Set**) + +Hides the property from the form designer property browser, but leaves it visible in other browsing contexts such as the Object Browser. This is the twinBASIC equivalent of VBA/Access run-time-only properties that do not appear at design time. + +To hide a property from all browsing contexts, use [**Hidden**](#hidden) instead. + +_Source threads: 1387416025892393042 · confidence: high_ +_Date range: 2025-06-25_ +_Reviewer note: The existing [Hidden] entry in Attributes.md lists applicability as Class/CoClass/Interface only. The Discord finding states [Hidden] on a Property Let hides it from all browsing contexts. Verify whether the applicability of [Hidden] should be expanded to cover Property procedures, and whether [NonBrowsable] is implemented and behaves as described. Both attributes need to be checked against the .twin source._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +### Example: detecting build completion + +Because the IDE does not produce a visible alert when a build fails with the DEBUG CONSOLE panel closed, a `[RunAfterBuild]` handler can serve as a programmatic signal. The handler is called after every build attempt --- including failed ones --- so comparing the output file's modification timestamp to a known good time detects whether a new binary was produced: + +```tb +[RunAfterBuild] +Public Sub AfterBuild() + If FileDateTime(App.LastBuildPath) >= Now - TimeSerial(0, 0, 5) Then + MsgBox "Build succeeded: " & App.LastBuildPath + Else + MsgBox "Build may have failed --- check the Debug Console." + End If +End Sub +``` + +> [!NOTE] +> If the DEBUG CONSOLE panel is closed when a build fails, the IDE returns silently to its idle state with no dialog or notification. Keep the DEBUG CONSOLE open during builds, or use a `[RunAfterBuild]` handler to check the result programmatically. + +_Source threads: 1414587677574959165 · confidence: medium_ +_Date range: 2025-09-08 to 2025-09-09_ +_Reviewer note: The timestamp comparison heuristic in the example is illustrative; verify whether `App.LastBuildPath` and `FileDateTime` behave as expected immediately after a build. Confirm that `[RunAfterBuild]` fires even on failed builds._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The function runs in the same process as the build, with full access to the language and project code. Common uses include invoking `signtool` to sign the output binary, running `git` commands to tag the build, or copying the output file to a deployment directory. `App.LastBuildPath` returns the path of the built output. + +_Source threads: 1431348682271363182 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: Insert after the existing RunAfterBuild section body (around line 589 of docs/Reference/Attributes.md). Verify that App.LastBuildPath is the correct property name and that the function runs in-process (not as a separate invocation of the IDE)._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> The three-argument (table, name, value) form of **[PopulateFrom]** requires the JSON source to be an object containing a named array of objects (e.g. `{ "events": [{"id": 12, ...}] }`). When the JSON source is a plain list of objects (e.g. `[{"id": 12, ...}]`) --- the default format for a twinBASIC STRING resource --- neither the three-argument form (raises TB5083: *JSON: accessor is invalid*) nor the two-argument form (silently produces an empty enum) works correctly. The workaround is to add the enum member IDs manually. + +_Source threads: 1455320119239643390 · confidence: medium_ +_Date range: 2025-12-29_ +_Reviewer note: Insert in the PopulateFrom section of Attributes.md, after the existing JSON structure example. Confirm whether a future fix is planned or if the limitation is by design._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> A common interop pattern for Windows APIs that return a variable-length array embedded in a struct (for example, `PROCESS_HANDLE_SNAPSHOT_INFORMATION.Handles`) is to declare the fixed-size trailing member with a nominal bound of `(0 To 0)`, then apply `[ArrayBoundsChecks(False)]` on the procedure that reads from it. With bounds checking disabled, indexing beyond the declared dimension is safe as long as the index stays within the actual runtime length reported by the API. + +_Source threads: 1459605334502150206 · confidence: high_ +_Date range: 2026-01-10_ +_Reviewer note: Insert after the existing body text of the ## ArrayBoundsChecks section (around line 50 of Attributes.md). Verify the pattern against the PROCESS_HANDLE_SNAPSHOT_INFORMATION example in the twinBASIC source or sample projects before publishing._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Only binds this name to a callsite when the caller uses the dollar-sign string-suffix form of the name. For example, a function named `Error` decorated with `[BindOnlyIfStringSuffix]` binds to `Error$()` but not to `Error()`. Used in the twinBASIC runtime library (Conversion module) to correctly dispatch the `Error$` variant. Complements [BindOnlyIfNoArguments](#bindonlyifnoarguments). + +_Source threads: 1462420288280068323 · confidence: high_ +_Date range: 2026-01-18 to 2026-01-22_ +_Reviewer note: This text should replace or extend the currently empty body of the ## BindOnlyIfStringSuffix section in Attributes.md (the section exists with syntax and applicability lines but no explanatory prose). Verify usage against the Conversion module .twin source._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Makes the class available in the twinBASIC IDE toolbox and gives it full ActiveX control behavior (Extender, siting, container notification). A class that compiles as an OCX but omits `[COMControl]` registers and instantiates with **New** as an ordinary COM object, but does not appear in the toolbox and cannot be sited on a form at design time. + +_Source threads: 1491119090306256906 · confidence: medium_ +_Date range: 2026-04-07 to 2026-04-13_ +_Reviewer note: This draft is a description body for the sparse [COMControl] entry in Attributes.md (currently only a syntax line with no prose). Reviewer should verify the exact semantics --- in particular whether [COMControl] applies to Class, CoClass, or Interface (the current page says Interface; the finding implies it applies to a class compiled into an OCX). Confirm against the .twin source._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> An `[AppObject]` class is not instantiated at program startup. `Class_Initialize` runs only when one of the class's members is first accessed --- initialization is deferred until first use. A project may define more than one `[AppObject]` class; they do not conflict with each other. This makes `[AppObject]` suitable for lazy-loading expensive resources, such as querying extension entry points at runtime, because the cost is paid only on first access. + +_Source threads: 1125359845936205844 · confidence: high_ +_Date range: 2026-01-09_ +_Reviewer note: This note targets the AppObject section of Attributes.md (the `## AppObject` heading at line 31). Confirm that the lazy-initialization behavior is specific to [AppObject] on CoClass and not a general [PredeclaredID] trait, and verify it applies to user-defined [AppObject] CoClasses, not just the built-in Global._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> +> **[TypeHint]** accepts a comma-separated list of enum type names, not just a single type. This is useful for Win32 API parameters that combine flags from several independent enumerations --- for example, a `dwDesiredAccess` parameter that accepts members from both a generic-rights enum and a file-specific-rights enum. All listed enumerations appear together in the Intellisense popup. +> +> ```tb +> Public Declare PtrSafe Function CreateFileW Lib "kernel32" ( _ +> ByVal lpFileName As LongPtr, _ +> [TypeHint(GenericRights, StandardAccessTypes, FileAccessRights)] _ +> ByVal dwDesiredAccess As Long, _ +> ByVal dwShareMode As Long, _ +> ByVal lpSecurityAttributes As LongPtr, _ +> ByVal dwCreationDisposition As Long, _ +> ByVal dwFlagsAndAttributes As Long, _ +> ByVal hTemplateFile As LongPtr) As LongPtr +> ``` + +_Source threads: 1106637635818094733 · confidence: high_ +_Date range: 2023-05-12 to 2023-05-24_ +_Reviewer note: This addition targets the `## TypeHint` section in Attributes.md (around line 633). Confirm that the multiple-enum form is in the current compiler --- the thread is from 2023 but the feature may have been present since then._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> twinBASIC's linker writes `Windows NT 4.0` into the OS version and Subsystem version fields of the `IMAGE_OPTIONAL_HEADER` in compiled executables, DLLs, and OCX files. Most applications are unaffected, but projects that require a specific OS or subsystem version in the PE header --- such as kernel drivers --- must patch these fields after the build. A `[RunAfterBuild]`-decorated procedure runs automatically after each successful build (see [RunAfterBuild](#runafterbuild)) and receives the output path through `App.LastBuildPath`, making it straightforward to apply any post-build patch without a separate build script. + +_Source threads: 1222086643737956494 · confidence: medium_ +_Date range: 2024-03-26_ +_Reviewer note: Verify the exact OS/Subsystem version string written by the twinBASIC linker, and confirm that App.LastBuildPath is the correct API for accessing the build output path from a [RunAfterBuild] procedure. The original workaround module is at https://github.com/fafalone/SetPEImageProps._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1427261753544409168] + +One common use for **[RunAfterBuild]** is to merge shell extension registration data immediately after compilation: + +```tb +[RunAfterBuild] +Public Sub RegisterExtension() + ' Merge the .reg file that registers this control as a shell extension. + Shell "reg import """ & App.LastBuildPath & "\\..\\ QuickRename.reg""" +End Sub +``` + +This pattern applies to any project that needs post-build steps: COM registration, file copying, or code signing. `App.LastBuildPath` holds the path to the compiled binary, so the sub does not need to hard-code a build output directory. + +_Source threads: 1292578507674746951 · confidence: high_ +_Date range: 2024-10-06_ +_Reviewer note: Draft inserts after the existing one-line description of [RunAfterBuild] in Attributes.md (around line 589). Verify the App.LastBuildPath usage is syntactically correct for a shell call and that the surrounding code example matches the site's tb code-fence convention._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951] + +> [!NOTE] +> +> `[MustBeQualified]` can be applied to a **Module** (forcing callers to qualify member references with the module name) but cannot be applied to interfaces. Interface methods have no module scope, so this attribute has no equivalent for them. In large Win32 API definition libraries that define many interfaces, members from those interfaces will appear unqualified in Intellisense and cannot be suppressed with this attribute. + +_Source threads: 1427261753544409168 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: The existing MustBeQualified entry says 'Applicable to: procedure' but the finding describes module-level use. Reviewer should verify: (1) whether the attribute also applies to Module declarations (not just individual procedures), and (2) confirm the interface limitation is accurate in the current compiler._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1432054854016045096, 1445969212698005648] + +When a DLL is placed next to the project file, the runtime may not locate it because Windows does not search the application directory in all circumstances. Placing `[SetDllDirectory(True)]` on the first **Declare** from that DLL instructs the loader to include the application path in its search. The attribute only needs to appear on one declaration per DLL; subsequent declarations in the same module do not need it. + +```tb +[ SetDllDirectory (True) ] +Public Declare Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As Long) +Public Declare Sub sqlite3_close Lib "sqlite.dll" (ByVal handle As Long) +``` + +In VB6, the equivalent requires declaring and calling the Win32 `SetDefaultDllDirectories` API with the `LOAD_LIBRARY_SEARCH_APPLICATION_DIR` flag before the first call into the DLL. + +_Source threads: 1140151535217680436 · confidence: high_ +_Date range: 2023-08-13_ +_Reviewer note: Insert this example block immediately after the SetDllDirectory attribute description at the #setdlldirectory anchor in Attributes.md. Verify the 'only needs to appear on one declaration per DLL' claim against the .twin source or compiler behaviour._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1445969212698005648] + +Under the **ArrayBoundsChecks** section, add an example block: + +### Win32 flexible array member pattern + +Some Win32 structures end with a variable-length trailing array (a C-style flexible array member). Declare the field with a single-element array inside the UDT, then apply **[ArrayBoundsChecks(False)]** to any procedure that reads or writes elements beyond index 0: + +```tb +' UDT that mirrors a Win32 structure with a trailing array +Private Type WLAN_AVAILABLE_NETWORK_LIST_V2_sa + NumberOfItems As Long + Index As Long + Network(0) As WLAN_AVAILABLE_NETWORK_V2 +End Type + +' Access the trailing elements through a pointer cast +[ArrayBoundsChecks(False)] +Private Sub ParseNetworkList(ByVal ptr As LongPtr) + With CType(Of WLAN_AVAILABLE_NETWORK_LIST_V2_sa)(ptr) + Dim i As Long + For i = 0 To .NumberOfItems - 1 + Debug.Print .Network(i).dot11Ssid.ucSSID + Next i + End With +End Sub +``` + +Placing the attribute on the UDT field itself is not yet supported; it must be placed on the calling procedure. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-10-26_ +_Reviewer note: The field-level attribute placement was a feature request acknowledged by WaynePhillipsEA; confirm whether it has since been implemented before publishing this note about the limitation._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1432054854016045096] + +### Example: reading variable-length C-style arrays + +Win32 APIs often return structures whose last field is a flexible array --- a field declared with a fixed size of 1 in the type definition but allocated larger at run time. `[ArrayBoundsChecks(False)]` makes it possible to traverse the full array without a runtime bounds error. + +Declare the UDT with the last field sized to 1, allocate a **Byte** array large enough for the full data, overlay the UDT on the buffer using `CType(Of ...)`, then iterate: + +```tb +[ArrayBoundsChecks(False)] +Private Sub ReadHandles() + Dim buf() As Byte + ' --- allocate buf and fill it via the API --- + With CType(Of SYSTEM_HANDLE_INFORMATION_EX)(VarPtr(buf(0))) + Dim i As Long + For i = 0 To .NumberOfHandles - 1 + Debug.Print .Handles(i).HandleValue + Next i + End With +End Sub +``` + +An alternative that avoids a fixed-size array declaration entirely: allocate via `CoTaskMemAlloc`, declare the last field as a plain non-array type (for example, `FirstHandle As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX`), and compute element addresses manually: + +```tb +Dim pEntry As LongPtr = VarPtr(info.FirstHandle) + i * LenB(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) +Dim entry As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +entry = CType(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)(pEntry) +``` + +Both patterns require `[ArrayBoundsChecks(False)]` (first) or no array at all (second). Apply `[ArrayBoundsChecks(False)]` at the narrowest scope --- a single procedure rather than a whole module --- to limit exposure. + +_Source threads: 1445969212698005648 · confidence: high_ +_Date range: 2025-12-04_ +_Reviewer note: This example uses hypothetical Win32 type names for illustration. A reviewer should verify the exact struct names match the WinDevLib or twinBASIC definitions before publishing, and confirm both code paths compile correctly._ + +--- + +## docs/Reference/CEF/index.md · after-remarks + +> [!NOTE] +> The CEF runtime files have no system-wide installer and do not register in a shared location. Both the 32-bit and the 64-bit runtime distributions can be present on the same machine at the same time, each in its own folder. A project targeting both architectures places the appropriate runtime files alongside each compiled binary. + +_Source threads: 1503736509713481799 · confidence: high_ +_Date range: 2026-05-12_ +_Reviewer note: Place this note in or near the 'Runtime files' / 'Installing runtime files' subsection rather than at the top of the page._ + +--- + +## docs/Reference/Compiler Constants.md · after-remarks + +## Target OS and compiler constants + +The project's **Target OS** setting controls the minimum OS version recorded in the compiled PE header and gates certain runtime features --- for example, control transparency requires targeting Windows 8 or later. As of BETA 844, the Target OS value is not exposed as a conditional compilation constant, so code cannot branch on the target OS at compile time using `#If`. + +> [!NOTE] +> x64 (AMD64) builds cannot target Windows 2000, which never shipped a consumer 64-bit edition. The practical minimum for x64 builds is Windows XP x64 Edition. Only 32-bit twinBASIC builds can target Windows 2000. This is an OS-level constraint, not a twinBASIC limitation. + +_Source threads: 1389961632846843988 · confidence: high_ +_Date range: 2025-07-04 to 2025-07-06_ +_Reviewer note: Verify whether a Target OS compiler constant has been added since BETA 844 (July 2025). The page uses a non-standard format (## headings without frontmatter sections) --- match the existing style when inserting._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> +> A procedure used as an **AddressOf** callback target does not need to be **Public**. Declaring it **Private** restricts its visibility within the module while still allowing it to be invoked by the Windows API through the function pointer. + +_Source threads: 1091707854488096851 · confidence: low_ +_Date range: 2023-04-03_ +_Reviewer note: Source finding is low-confidence and mentions SetTimer/KillTimer specifically (VBA package, null symbol). Mapped to AddressOf as the closest reference page. Verify against the twinBASIC compiler behaviour --- in classic VBA, AddressOf targets must be in a standard Module and Public; confirm that Private callbacks work correctly in twinBASIC's implementation before publishing._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> When using Tab to autocomplete a function name after the **AddressOf** operator in the twinBASIC IDE, the editor may incorrectly append empty parentheses to the function name, producing `AddressOf FunctionName()` instead of the correct `AddressOf FunctionName`. The trailing parentheses are invalid in this position and must be deleted manually. + +_Source threads: 1175257978023182426 · confidence: medium_ +_Date range: 2023-11-18_ +_Reviewer note: IDE editor bug reported in beta 423. Verify whether this has been fixed in a later release before publishing._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> Applying **AddressOf** to a procedure obtained through a **Declare** statement introduces compiler-generated indirection on top of the raw entry point. The address produced is not the same as the pointer returned by `GetProcAddress` for the same function. When this indirection causes a delegate invocation to behave differently from a direct call---for example, a caller-allocated buffer pattern that succeeds as a direct call but silently returns an empty result through the delegate---obtain the pointer explicitly with `GetProcAddress` and cast it to the target delegate type: +> +> ```tb +> Dim lpfn As LongPtr = GetProcAddress(hLib, "FunctionName") +> Dim d As YourDelegate = CType(Of YourDelegate)(lpfn) +> ``` +> +> This produces a clean function pointer with no extra indirection, and the delegate invocation then behaves identically to a direct call. + +_Source threads: 1499900664275275969 · confidence: high_ +_Date range: 2026-05-01 to 2026-05-02_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> When `AddressOf` targets a class member (e.g. `AddressOf myInstance.Func`), twinBASIC generates an on-the-fly stub that captures the specific object instance and dispatches through it. The returned pointer is therefore unique per instance---it is **not** the address of the function as it appears in the vtable. Call the returned pointer as a callback and the implicit `Me` argument is populated automatically for that object. To obtain the raw vtable entry address instead, use pointer arithmetic on `ObjPtr`; that is only necessary for low-level COM work and is not required for ordinary callback use. + +_Source threads: 1136660342739566635 · confidence: high_ +_Date range: 2023-08-03 to 2023-08-16_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> **AddressOf** does not return the raw machine address of a procedure. twinBASIC creates a stub thunk behind the scenes so that calling conventions and the implicit object pointer are handled correctly when the callback is invoked. VB6 code that identifies a callback by ordinal position rather than by name --- for example, passing the integer `1` to mean "the last Sub in the file" --- cannot be ported mechanically: that pattern relied on VB6's internal vtable numbering, which twinBASIC does not reproduce. Replace the ordinal reference with an explicit **AddressOf** *ProcedureName* expression, where *ProcedureName* is the actual name of the target procedure. In VB6 projects using this pattern the target was typically the last Sub or Function declared in the module. + +_Source threads: 1291033244753727490 · confidence: high_ +_Date range: 2024-10-02_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1156840378213867620] + +> [!NOTE] +> +> In VB6, **AddressOf** is restricted to procedures in standard modules (BAS files). Using it inside a class module or form requires self-subclassing workarounds such as a separate forwarding module. In twinBASIC that restriction does not apply --- **AddressOf** accepts any method regardless of its containing module type, making patterns like Win32 `SetWindowSubclass` straightforward to implement directly on a class. + +_Source threads: 1253786438454214788 · confidence: high_ +_Date range: 2024-06-25_ +_Reviewer note: The existing twinBASIC enhancements section already documents that AddressOf works on class, form, and UserControl members. This note adds the VB6 restriction context. Check whether it duplicates or enriches the existing text before inserting, and consider integrating into the existing bullet point instead._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788] + +> [!NOTE] +> **64-bit only:** In VBA 64-bit (and twinBASIC running in 64-bit mode against a VBA-hosted runtime), the addresses of procedures declared in a standard module are uninitialized when first queried via **AddressOf** --- until those procedures have been called at least once from within Basic code. If a Windows API (for example, one that invokes a dialog callback or a WndProc) calls through the address before any Basic code has invoked the procedure, the address is not valid and the program crashes. +> +> The workaround is to add a public no-op `Sub` to the same module as the callbacks or WndProcs, then call it from early initialization code (for example, `Class_Initialize`) before passing any **AddressOf** addresses to external APIs. This forces the runtime to initialize the procedure addresses. +> +> This issue affects any 64-bit project that passes module-level function addresses as callbacks or WndProcs to Windows APIs. + +_Source threads: 1156840378213867620 · confidence: high_ +_Date range: 2023-09-28_ +_Reviewer note: Verify whether this 64-bit uninitialized-address behavior still occurs in current twinBASIC builds, or whether it has been fixed. The finding was reported against VBA 64-bit hosted code; behavior in native twinBASIC 64-bit may differ._ + +--- + +## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1459159860875624595] + +> [!NOTE] +> +> **POINTAPI compatibility change (BETA 947 / v9.2.634.0):** `POINTAPI` was changed from a standalone UDT to a type alias (`Public Alias POINTAPI As POINT`). Because type aliases are interchangeable for simple assignments and scalar parameters, most code continues to work; however, passing a `POINTAPI()` array to an API parameter declared as `POINT()` raised Runtime Error 5 until BETA 953, where the underlying mismatch was corrected. If porting code that uses `POINTAPI` arrays, prefer declaring both the array variable and the API parameter with the same type name (`POINT` or `POINTAPI`) to avoid the issue on intermediate builds. + +_Source threads: 1455209665733464247 · confidence: high_ +_Date range: 2025-12-29 to 2026-01-17_ + +--- + +## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1455209665733464247] + +> [!NOTE] +> The twinBASIC IDE automatically resolves alias chains when the underlying type is itself an alias. Writing `Public Alias MyPtr As LongPtr`, for example, causes the editor to rewrite `LongPtr` to `Long` or `LongLong` depending on the target architecture, because `LongPtr` is itself an alias. This behavior applies to any alias-of-alias, not only `LongPtr`. The rewrite is silent and occurs as soon as the source file is edited. The issue was a regression in BETA 950 and was fixed in BETA 951. + +_Source threads: 1459159860875624595 · confidence: high_ +_Date range: 2026-01-09 to 2026-01-16_ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> The compiler unconditionally generates implementations of the three core `IUnknown` members --- `AddRef`, `Release`, and `QueryInterface` --- for every **Class**. `AddRef` and `Release` manage the object's reference count and release all associated memory when the count reaches zero; the compiler enforces this by raising a "duplicate implementation of member" error if user code defines `AddRef` or `Release`. `IDispatch` members, by contrast, can be manually implemented. If custom `IUnknown` forwarding is required (for example, to expose a vtable-based interface that includes `IUnknown` members at known offsets), the only supported path is vtable patching at runtime. + +_Source threads: 1060905587577208863 · confidence: high_ +_Date range: 2023-01-06_ +_Reviewer note: Verify against the compiler source or a test project that AddRef/Release genuinely raise a compile error, and that QueryInterface does not (the source described that as a bug rather than a supported pattern)._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> twinBASIC dispatches class method calls through the native COM vtable function pointer, as the COM specification requires. VBA64 (Office 2010 with service packs and later) does not: when it detects that the target object is owned by its own runtime, it bypasses the vtable and jumps directly to the p-code stream. As a result, replacing a vtable slot with a pointer to custom executable memory works correctly in twinBASIC but has no effect in VBA64 --- and may crash it, because the VBA64 runtime navigates from the original vtable function pointer to the p-code stream and expects the pointer to be intact. This optimization was absent in Office 2010 RTM, where native vtable calls were used and vtable slot replacement worked. + +_Source threads: 1157647794484543589 · confidence: high_ +_Date range: 2023-09-30 to 2023-10-01_ +_Reviewer note: Placement on the Class page is reasonable but this could alternatively live on a dedicated low-level/interop features page if one is added. The p-code navigation detail (vtable pointer as a key to locate the p-code stream) is confirmed by WaynePhillipsEA (twinBASIC author) in the thread._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> A **Public** array member of a class is automatically converted by the compiler into a **Property Get** / **Property Let** pair. The **Property Get** returns a *copy* of the array, not a reference to the storage inside the object. As a result, writing to an element through the returned copy --- or calling **ReDim** on it --- has no effect on the object's own data, and no compile-time error is raised. For example, `Set obj.Children(1) = node` silently discards the assignment, and `ReDim obj.arr(4)` raises a run-time error because `obj.arr` evaluates to a copy, not the original storage. + +To expose a mutable array from a class, declare the field **Private** (or **Protected**) and provide dedicated mutation methods: + +```tb +Private Children() As Node + +Public Sub SetChild(index As Long, n As Node) + Set Children(index) = n +End Sub +``` + +Arrays inherited from a base class are not affected --- they behave as direct field accessors. + +_Source threads: 1481282096877146244 · confidence: high_ +_Date range: 2026-03-11_ +_Reviewer note: Verify that the base-class exception (inherited arrays not subject to the copy behavior) is still accurate in the current compiler._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> Calling any method on a **Nothing** reference raises a run-time error at the call site --- the method body is never entered. All class methods are virtual (vtable) calls, and the runtime checks the instance before dispatching. A method body that reads `VarPtr(Me) = 0` to detect a **Nothing** caller will therefore never execute. +> +> Two workarounds are available: +> - Declare the method as `Protected` (which removes its vtable entry) and call it through [`UnprotectedAccess`](../../Modules/HiddenModule/UnprotectedAccess) at the call site. +> - Wait for proper static (`Shared`) class members, which are not yet implemented. +> +> Note also that `IUnknown::AddRef` and `Release` are called implicitly on the `Me` argument in member prologue and epilogue, so additional issues arise if a null instance reaches the method body by any other means. + +_Source threads: 1458787436607045693 · confidence: high_ +_Date range: 2026-01-08_ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1485008719719698604] + +> [!NOTE] +> A `.cls` file can contain only one class definition, matching VB6 behaviour. A `.twin` file may contain multiple **Class** blocks --- along with [**Interface**](Interface), [**CoClass**](CoClass), [**Module**](Module), and [**Alias**](Alias) declarations --- in the same file. Classes defined in a `.twin` file are full, normal classes, accessible from anywhere in the project. + +_Source threads: 1459196491380818071 · confidence: high_ +_Date range: 2026-01-09 to 2026-01-10_ +_Reviewer note: The existing Class.md body already notes that .twin files may share a file with Interface/CoClass/Module, but does not explicitly say multiple Class blocks are allowed in one .twin file. Verify this and merge with the existing paragraph rather than duplicating it._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071] + +> [!NOTE] +> Generic classes do not support inheritance. A class declared with a type parameter (e.g. `Class Derived(Of T)`) cannot use `Inherits`. Attempting to do so does not produce an immediate compile error, but accessing any inherited `Protected` member from within the generic derived class produces error TB5079 "Unrecognized symbol" when the generic class is instantiated. `Public` members of the base class may appear to work, which can mask the underlying lack of support. The limitation only surfaces at the point where the first instance of the generic class is created. Inheritance support for generic classes is planned for a future release. + +_Source threads: 1485008719719698604 · confidence: high_ +_Date range: 2026-03-21 to 2026-03-23_ +_Reviewer note: Verify against current compiler behaviour -- specifically whether TB5079 is the exact error number and whether Public member access truly works when Protected fails._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> In early twinBASIC releases, a `Const` declaration that referenced an enum member defined in a separate standard module (for example, `Private Const MyConst As Long = MyEnum.MyEnumValue`) could fail to compile when the compiler processed the class module before the standard module declaring the enum. The failure was evaluation-order-dependent: adding, removing, or reordering modules in the project could change whether it compiled. This was fixed by the twinBASIC maintainer shortly after December 2022. + +_Source threads: 1053323243085385828 · confidence: high_ +_Date range: 2022-12-16 to 2023-01-07_ +_Reviewer note: Describes a now-fixed compiler bug. Verify against release notes around January 2023 to confirm the fix landed and the version number, if it should be cited._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> twinBASIC warns when a hex literal is implicitly coerced from **Integer** to **Long** and sign-extended --- for example, `&H8000` becoming `&HFFFF8000`. This warning is suppressed when the hex value appears in a **Const** declaration, because the constant retains its **Integer** representation and no coercion occurs at that point. The warning fires only at the site of the actual coercion to **Long**, not at the declaration. + +_Source threads: 1071216635966402610 · confidence: high_ +_Date range: 2023-02-04 to 2023-02-07_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> A `Public Const` expression that references an enum member defined in another module may produce an "Error evaluating constant expression" compile error if the module containing the enum sorts alphabetically after the module containing the constant. The compiler evaluates constant expressions during an early pass; if the enum's defining module has not yet been processed, the value is not known at that point. To avoid this, either place the enum and the constants that depend on it in the same module, or name the module containing the enum so that it sorts alphabetically before the module that references it. + +_Source threads: 1082234628385034240 · confidence: medium_ +_Date range: 2023-03-06_ +_Reviewer note: Reported behavior included non-deterministic failure across IDE restarts, suggesting the module load order may not be strictly alphabetical in all cases. Verify whether this has been fixed in a later twinBASIC build before publishing._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Before twinBASIC BETA 298, a literal value that exceeded the range of its type-declaration character suffix (for example, `&H10012002%`, where `%` forces **Integer** type) was silently truncated rather than reported as a compiler error. The same code raises an error at edit time in VBA and VBx. The truncation affected both **Const** declarations and variable assignments. The bug was fixed in BETA 298; such out-of-range literals now produce a compiler error as expected. + +_Source threads: 1096942103180869632 · confidence: high_ +_Date range: 2023-04-15 to 2023-05-12_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> A self-referential `Const` declaration --- one where the constant name appears on the right-hand side of its own initializer, for example `TVN_BEGINRDRAG = TVN_BEGINRDRAG` instead of the intended `TVN_BEGINRDRAG = TVN_BEGINRDRAGW` --- causes the twinBASIC IDE syntax highlighter to enter an endless loop. The symptom is that all syntax coloring stops working and every token renders in a single color; the broken state persists after restarting the compiler. Correcting the self-referential assignment restores normal highlighting. This bug was fixed in BETA 312. + +_Source threads: 1110426304710459442 · confidence: high_ +_Date range: 2023-05-23_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Constants whose *expression* references a constant declared in a different module may intermittently fail to evaluate on compiler startup, producing a "cannot evaluate constant expression" error. The failure is non-deterministic: restarting the compiler without changing any source can alternate between reporting the error and not. A reliable workaround is to declare a private copy of the base constant in the same module that uses it, eliminating the cross-module dependency. + +_Source threads: 1201555639005106257 · confidence: high_ +_Date range: 2024-01-29 to 2026-02-05_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> +> **Hexadecimal literals and type suffixes.** A hexadecimal literal is typed by its magnitude. The literal `&H8000` fits in a 16-bit **Integer** and is therefore interpreted as **Integer** `-32768`, not **Long** `32768`. Declaring `Const SOMECONSTANT As Long = &H8000` triggers warning TB0012 (*Possible misuse of hexadecimal literal notation*) because the literal is being widened --- a sign that the value is almost certainly wrong. The correct form is `&H8000&`, where the trailing ampersand forces the literal to **Long** type, giving the value `32768`. +> +> The practical consequence is that comparisons or flag checks using the unqualified form can evaluate to unexpected results --- for example, `&H8000 >= &H100` evaluates to `False` because `&H8000` is `-32768` as an **Integer**. + +_Source threads: 1382265105621979166 · confidence: high_ +_Date range: 2025-06-11 to 2025-06-25_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Certain extreme numeric literals require special handling when used in **Const** declarations: +> +> **Currency.** The `@` suffix is required to tell the compiler that the literal is **Currency**. The accepted minimum value constant is `-922337203685477.5807@`; the literal `-922337203685477.5808@` is rejected by the compiler. This matches VB6 behavior. +> +> **LongLong.** The minimum **LongLong** value cannot be written as a single negative literal. Use the expression form instead: +> ```tb +Public Const MINLONGLONG As LongLong = (-9223372036854775807^ - 1) +``` +> or a string literal: `Public Const MINLONGLONG As LongLong = "-9223372036854775808"`. +> +> **Decimal.** There is no type-suffix character for **Decimal**, so extreme-range constants must be written using `CDec` with a string argument: +> ```tb +Public Const G_MAX_DECIMAL As Decimal = CDec("79228162514264337593543950335") +Public Const G_MIN_DECIMAL As Decimal = CDec("-79228162514264337593543950335") +``` + +_Source threads: 1487572362898444458 · confidence: high_ +_Date range: 2026-03-28 to 2026-03-30_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1466129455276757117] + +> [!NOTE] +> Enum types cannot be used as the declared type of a **Const**. A declaration such as `Const x As SomeEnum = SomeEnum.Value` raises a compile-time error (`Expected type name`). This restriction is inherited from VB6. Because all enum values are backed by **Long**, the workaround is to omit the **As** *type* clause and let the constant take on the type of its initializer expression, or to declare it explicitly **As Long**. + +_Source threads: 1069548868200583168 · confidence: high_ +_Date range: 2023-01-30_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168] + +> [!NOTE] +> A hex literal without a type suffix is assigned the type **Integer** by the compiler (for compatibility with VBA and VB6). A value such as `&H8000` is therefore an **Integer** constant whose high bit is set. When the constant is used in a **Long** context, the runtime sign-extends the **Integer** value, so `&H8000` becomes `&HFFFF8000` rather than `&H00008000`. Declaring the constant `As Long` does not change this --- the type of the *literal* itself determines the intermediate representation. The correct fix is to append the **Long** type-declaration character to the literal: `&H8000&`. This affects any hex constant whose value is `>= &H8000`. + +_Source threads: 1466129455276757117 · confidence: high_ +_Date range: 2026-01-28_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!WARNING] +> +> **`RtlInitUnicodeString` does not allocate memory.** When porting VB6 code that uses Windows NT Native API `UNICODE_STRING` structs, verify that every call to `RtlFreeUnicodeString` corresponds to an API that explicitly heap-allocates the string buffer --- such as `RtlDuplicateUnicodeString`. `RtlInitUnicodeString` only stores the length and address of an existing buffer into the `UNICODE_STRING` struct; it performs no heap allocation. Calling `RtlFreeUnicodeString` on a `UNICODE_STRING` that was initialized with `RtlInitUnicodeString` is incorrect and causes a hard crash in twinBASIC. The same misuse may appear to succeed silently in VB6 due to runtime memory layout differences. + +_Source threads: 1053654998556348457 · confidence: high_ +_Date range: 2022-12-17 to 2023-01-07_ +_Reviewer note: This is a Win32 NT Native API porting gotcha (ntdll.dll: RtlInitUnicodeString / RtlFreeUnicodeString / RtlDuplicateUnicodeString). The finding was confirmed by Wayne Phillips as an API usage error, not a twinBASIC bug. Placement on the Declare page is reasonable but the site has no dedicated Win32 API porting guide; a reviewer may prefer to place this on the Features/Advanced/API-Declarations page instead._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a declared function's output parameter is typed `ByRef pUnk As IUnknown` and the caller passes a variable of a more specific interface type (for example, `IPicture`), the compiler inserts an implicit **QueryInterface** call at the callsite after the function returns. If **QueryInterface** succeeds the variable is populated with the correct interface pointer. If it fails --- for example, the returned object does not implement the requested interface --- a runtime error such as "No such interface supported" or "Type mismatch" is raised. This behavior is identical in VB6 and twinBASIC. A practical consequence is that when calling an API such as `OleCreatePictureIndirect`, passing `IID_IUnknown` as the `riid` argument is sufficient: the implicit QI at the callsite performs the cast to the caller's declared type, so supplying `IID_IPicture` explicitly is redundant. + +_Source threads: 1053698170846183434 · confidence: medium_ +_Date range: 2022-12-17_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> Prior to BETA 269, a **Public Declare** and a **Public Enum** with the same base name (compared case-insensitively --- for example, `ShowWindow` and `SHOWWINDOW`) in separate modules within the same project caused an ambiguity error. The compiler's symbol resolver is context-free: it looks up a name in the symbol table without examining how the name is used at the call site (whether it is followed by a dot, appears as a type annotation, or is being called as a procedure), so it cannot distinguish between a callable declaration and a data type of the same name when both are in scope. This was fixed in BETA 269. In earlier versions the workaround was to place the **Declare** and the conflicting **Enum** in the same module, or to qualify the symbol with its module name. + +_Source threads: 1084723164296261672 · confidence: high_ +_Date range: 2023-03-13_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a standard DLL function returns an array of UDTs directly as its return value, the caller may crash. The cause is a lifetime management issue at the DLL boundary: the array may be released before the caller has finished with it. A reliable workaround is to store the array in a `Private` module-level variable inside the DLL module, then return that variable. The module-level variable extends the array's lifetime beyond the function frame and prevents the premature deallocation. + +_Source threads: 1099208311313813534 · confidence: medium_ +_Date range: 2023-04-22_ +_Reviewer note: Needs verification against the twinBASIC runtime source to confirm the root cause (array released too soon at DLL boundary). The workaround (store in module-level variable) was reported as effective in practice, but the underlying cause has not been confirmed from first principles._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When writing a COM interface method that takes an optional output pointer (such as a D2D1\_TAG-style out-parameter), declaring the parameter as `ByRef tag As LongLong` and calling it with `ByVal 0` does not pass a null pointer on 32-bit (x86) builds. The `ByVal` modifier at the call site promotes the argument to a 64-bit value, so two 64-bit zero pushes land on the stack instead of two 32-bit null pointer pushes. The call returns with the stack misaligned, causing an access violation. Declare the parameter as `ByRef tag As Any` (the convention used by oleexp/olelib type libraries) so that `ByVal 0` correctly passes a 32-bit null pointer. + +_Source threads: 1101704869804511265 · confidence: high_ +_Date range: 2023-05-11 to 2023-05-12_ +_Reviewer note: This applies specifically to COM interface method declarations (Interface blocks), not only to Declare statements. If a dedicated page for COM interface method argument conventions exists or is added, consider moving this note there. Verify the claim against the twinBASIC compiler behavior on x86 targets._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When an ANSI Win32 API is declared and a UDT parameter contains a fixed-length string field (for example, `dmDeviceName As String * CCHDEVICENAME`), the compiler generates implicit ANSI-conversion procedures for the UDT. Before a compiler fix issued in a subsequent BETA release, those generated procedures did not explicitly return `S_OK` on success, leaving the return value of the procedure's final instruction (a pointer) in `EAX`/`RAX` to be interpreted as an `HRESULT`. On x86 this rarely caused a problem because pointer values seldom set the `0x80000000` failure bit. On x64, the larger address space means pointers frequently have that bit set, so the implicit conversion is treated as failed and the API call is aborted intermittently. +> +> Two workarounds are available: +> +> 1. Replace the fixed-length string field with a `Byte` array of the same length (for example, `dmDeviceName(CCHDEVICENAME - 1) As Byte`). +> 2. Switch to the Unicode variant of the API using `DeclareWide`, replacing `String` fields with `String` and `Integer` arrays in place of `Byte` arrays for wide-char fields. + +_Source threads: 1107676681449254952 · confidence: high_ +_Date range: 2023-05-18_ +_Reviewer note: Verify the exact BETA release version in which the compiler fix shipped. The finding says 'a subsequent BETA release' without a specific number. Also confirm that DeclareWide is the correct twinBASIC keyword for the Unicode workaround._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> In versions of twinBASIC before BETA 331, passing a `ByVal` expression as a named argument to a **Declare**-imported procedure caused a syntax error. For example, `lpOutput:=ByVal 0` was rejected. The workaround was to wrap the expression in parentheses: `lpOutput:=(ByVal 0)`. VB6 accepted the bare form without parentheses. This restriction was removed in BETA 331. + +_Source threads: 1117974216810188920 · confidence: high_ +_Date range: 2023-06-13_ +_Reviewer note: Verify the exact BETA 331 fix version against the twinBASIC changelog. The Discord thread attributed the fix to BETA 331 and confirmed it was a named-argument-only issue, but the release number should be confirmed before publishing._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a `Declare` statement loads a DLL from the application's own folder, the DLL may fail to load on subsequent IDE sessions even though it loads correctly on the first run after import. The DLL search path used during the initial session does not persist once the IDE is closed and reopened. Add the `[SetDllDirectory(True)]` attribute to the `Declare` statement to include the application's directory in the DLL search path: +> +> ```tb +> [SetDllDirectory(True)] +> Public Declare PtrSafe Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As LongPtr) +> ``` +> +> If the DLL is installed into a Windows system directory (`System32` or `SysWOW64`), the attribute is not needed. + +_Source threads: 1139937031775072307 · confidence: medium_ +_Date range: 2023-08-13 to 2023-08-14_ +_Reviewer note: Verify that [SetDllDirectory(True)] on a Declare statement is the canonical fix for this behavior and that the description in docs/Reference/Attributes.md (which says it 'allows searching the app path for the DLLs in the base app's declare statements') matches the behavior reported. The example uses PtrSafe for correctness on 64-bit; reviewer should confirm the attribute works on both 32-bit and 64-bit targets._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC supports the `_cdecl` calling convention natively in **Declare** statements. VB6 cannot call `_cdecl` DLL functions directly and requires a separate stdcall wrapper DLL instead. In twinBASIC, a direct **Declare** with the **CDecl** keyword replaces any such wrapper. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. + +_Source threads: 1140151535217680436 · confidence: high_ +_Date range: 2023-08-13_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Specifying a relative subfolder path in *libname* --- for example `Lib "subfolder\anyname.dll"` --- results in an "Error Loading DLL" at runtime. twinBASIC does not resolve the subfolder relative to the executable's location. The workaround is to call the Win32 `SetDllDirectory` API with the target directory before the first call that loads the library, or to use an absolute path in *libname*. The file extension does not have to be `.dll`; other extensions such as `.dat` are accepted provided the folder path is resolved correctly. + +_Source threads: 1148585708362149899 · confidence: medium_ +_Date range: 2023-09-05_ +_Reviewer note: Verify whether this limitation is still present in current builds, and whether there is a [SetDllDirectory] attribute documented on the Attributes page that already covers this workaround._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **MIDL type library type mapping** -- When a type library (TLB) compiled with MIDL is imported into twinBASIC, the MIDL pointer-width types do not map to twinBASIC's intrinsic **LongPtr** as they do conceptually in C: +> +> - `LONG_PTR` always maps to a 64-bit integer regardless of whether the TLB was compiled with `/env win32` or `/env win64`. +> - `__int3264` maps to **LongLong** in both 32-bit and 64-bit configurations. +> - `size_t` causes a parse error because it maps to an unsigned 64-bit value that twinBASIC cannot represent. +> +> As a result, no MIDL type currently maps to twinBASIC's intrinsic **LongPtr** when imported through a TLB. +> +> **Workaround 1 -- shared TLB:** Add `typedef [public] long LongPtr;` to the MIDL source. VB6 treats it as **Long** (4 bytes). twinBASIC gives its own intrinsic **LongPtr** priority over the TLB-defined alias when the name is used unqualified; if the type is referenced with its library qualifier (e.g. `mytypelib.LongPtr`), twinBASIC resolves it to the TLB's 4-byte **Long** definition instead. +> +> **Workaround 2 -- `void*` as `As Any`:** Arguments declared as `void*` in MIDL map to **As Any** in twinBASIC, independent of the TLB's compilation target. Passing a pointer `ByVal` with **As Any** works in both 32-bit and 64-bit builds. This requires callers to write `ByVal` explicitly on each call site, which is error-prone, and `void*` cannot be used inside UDTs --- it is unsupported in both VB6 and twinBASIC for that context. + +_Source threads: 1159270753468952576 · confidence: medium_ +_Date range: 2023-10-04 to 2023-10-05_ +_Reviewer note: Verify current behavior against twinBASIC source or a test TLB: in particular, confirm that LONG_PTR still maps to 64-bit rather than LongPtr, and that the void*/As Any behavior is still architecture-independent. The thread reports Beta 399 behavior; check whether any of these mappings have changed in later builds._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> `[PreserveSig(False)]` is supported on **Declare** statements in twinBASIC. When translating a type library whose COM methods return an HRESULT that should be hidden from tB callers, `[PreserveSig(False)]` is the correct annotation --- the compiler transforms the call so that the HRESULT is checked automatically and the last out-parameter is promoted to the function's return value. An earlier version of the twinBASIC typelib viewer incorrectly emitted a warning comment instead of the `[PreserveSig(False)]` attribute for such declarations; this was corrected in BETA 553. + +_Source threads: 1248884196575809578 · confidence: high_ +_Date range: 2024-06-08_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When a `Declare` statement passes a SafeArray pointer to a Windows API function such as `SafeArrayLock` or `SafeArrayUnlock`, declare the pointer parameter **ByVal**, not **ByRef**. Declaring it **ByRef** passes the address of the local variable that holds the pointer --- a double indirection --- which corrupts the lock and unlock operations. The symptom is that a class constructor that calls these functions appears to finish correctly (the instance looks valid inside the constructor body) but the caller receives **Nothing** or an instance in an incorrect state. + +_Source threads: 1270386953115930828 · confidence: medium_ +_Date range: 2024-08-06 to 2024-08-07_ +_Reviewer note: Verify against the SafeArrayLock / SafeArrayUnlock Win32 API signatures to confirm ByVal is correct for the SAFEARRAY* parameter. The finding is self-reported by the user after fixing their own bug; no maintainer confirmation in the thread._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC automatically redirects `Declare` statements that target `msvbvm60` by exported name to its own runtime equivalents, so VB6-compatible code runs without requiring `msvbvm60.dll` on disk. However, this redirect applies only when the `Alias` clause specifies an exported name. If the alias is an ordinal reference --- for example `Alias "#307"` --- twinBASIC does not recognise the declaration as a redirected call and will attempt to load the real `msvbvm60.dll` at runtime. VB6 code in the wild sometimes imports `msvbvm60` functions by ordinal (for example `PutMem4 Lib "msvbvm60" Alias "#307"`), so this limitation can affect VB6 migration projects. + +_Source threads: 1295648317820108831 · confidence: high_ +_Date range: 2024-10-15 to 2024-10-21_ +_Reviewer note: A twinBASIC maintainer (WaynePhillipsEA) described this as a bug that should be straightforward to fix. Verify whether this has been fixed in a later release before publishing; if fixed, the note may be removed or converted to a historical remark._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> On Windows Vista and Windows 7, including the `.dll` extension in the *libname* string (for example, `"cabinet.dll"`) causes a DLL search path error at runtime, preventing the DLL from loading. Omit the extension (use `"cabinet"` rather than `"cabinet.dll"`) when targeting those OS versions. This restriction does not apply to Windows XP, Windows 10, or Windows 11. The same issue can affect other DLLs listed in the import address table (IAT). Note that the failure occurs only in compiled executables; running from the twinBASIC IDE is not affected. + +_Source threads: 1310261549574656001 · confidence: high_ +_Date range: 2024-11-24 to 2024-12-14_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **ByVal with object variables differs from VB6.** In VB6, passing an object variable `ByVal` to a `Declare`-style API (for example a `CopyMemory` wrapper) treats the argument as the address of the reference slot --- equivalent to `ByVal VarPtr(o)`. In twinBASIC the same call behaves as `ByVal ObjPtr(o)`, dereferencing the object pointer first. Low-level object-pointer code that relies on `ByVal ` without an explicit pointer function will therefore produce different results between the two compilers. To be explicit: use `ByVal ObjPtr(o)` when the API needs the vtable/object address, and `ByVal VarPtr(o)` when it needs the address of the reference variable itself. + +_Source threads: 1356284960495108156 · confidence: medium_ +_Date range: 2025-03-31_ +_Reviewer note: Verify against twinBASIC compiler source or a reproducible test case before publishing. The distinction between ObjPtr and VarPtr semantics for ByVal object arguments is subtle and this finding is medium-confidence._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> From BETA 797 onward, `ByRef ParamArray args As Any()` is supported in **Declare** (without **CDecl**) for `__stdcall` functions that accept a `va_list`-style pointer. Before BETA 797, using this pattern without **CDecl** raised a compiler error requiring **Variant**; the workaround was to add **CDecl**, which caused a calling-convention mismatch for true stdcall functions. +> +> **CDecl** remains required for genuinely variadic functions that use the C `...` ellipsis calling convention. + +_Source threads: 1378220872405487636 · confidence: high_ +_Date range: 2025-05-31 to 2025-06-08_ +_Reviewer note: Verify that the fix landed in exactly BETA 797 and that the description of `ByRef ParamArray args As Any()` being valid for stdcall va_list-style declarations is accurate against the twinBASIC compiler source._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC does not silently coerce `Object` variables to COM interface types such as `IUnknown` when passing them to **Declare** functions. Passing an `Object` variable to a parameter declared `As IUnknown` (or any other specific COM interface) causes an access violation (&HC0000005) at run time. VB6 accepted this by performing a `QueryInterface` coercion automatically. In twinBASIC the types must match exactly: declare a local variable `As IUnknown`, obtain the reference into that variable, then pass it to the API call. If the calling code needs an `Object` afterwards, assign the result separately with `Set objVar = ppUnk`. + +_Source threads: 1444009884637986966 · confidence: high_ +_Date range: 2025-11-28_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When the `[ComExport]` attribute is applied to a `Declare` function whose return type is a type alias, parameter types were correctly exposed under the alias name in the exported type information, but the return type was resolved to its underlying primitive (for example, a `WinRT.LongPtr` return type appeared as `Long`). Interface methods with alias return types were not affected. Fixed in BETA 957. + +_Source threads: 1463135813574594730 · confidence: high_ +_Date range: 2026-01-20 to 2026-01-21_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When a **Declare** parameter is typed **As Any** and represents a null pointer, pass `vbNullPtr` rather than the integer literal `0`. Passing `0` only guarantees that the lower 16 bits of the argument slot are zero; the current twinBASIC compiler happens to zero-extend correctly, but an optimizing backend (such as the planned LLVM backend) may leave upper bits non-zero if it determines only the low 16 bits need satisfying. `vbNullPtr` is a native-pointer-sized zero and is always safe. + +_Source threads: 1505726092479041556 · confidence: high_ +_Date range: 2026-05-18_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **olepro32.dll** has no 64-bit version. When compiling in 64-bit mode, twinBASIC (as of BETA 311) automatically redirects any **Declare** that targets **olepro32.dll** to **oleaut32.dll**, where MSDN has long documented the same functions (such as `OleCreatePictureIndirect`). Existing VB6-derived code that declares these functions against **olepro32.dll** compiles and runs correctly in 64-bit mode without manual changes. + +_Source threads: 1110121787745894400 · confidence: high_ +_Date range: 2023-05-22_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> As of October 2025, the **PtrSafe** keyword is optional in twinBASIC for both 32-bit and 64-bit targets. A **Declare** statement without **PtrSafe** compiles for both architectures. The keyword is still accepted and still signals VBA7 x64 intent, but its absence is no longer a compile error on any target. Note that omitting **PtrSafe** while using 32-bit-only **Long** for pointer parameters still produces incorrect behaviour on x64 at runtime --- the compiler does not validate pointer-size correctness when **PtrSafe** is absent. +> +> **LongPtr** may be used in a **Declare** statement that does not carry **PtrSafe**. The requirement that **LongPtr** implies **PtrSafe** is a misconception: in 32-bit VBA7 (and in 32-bit or 64-bit twinBASIC), **LongPtr** is valid in non-PtrSafe declares. **PtrSafe** was only ever required by 64-bit VBA7; twinBASIC now makes it optional for all targets. + +_Source threads: 1421815804172701708 · confidence: high_ +_Date range: 2025-09-28 to 2025-10-15_ +_Reviewer note: The existing Declare page documents PtrSafe as "required in 64-bits" in its parameter table. That description should be updated to reflect that PtrSafe is now optional in twinBASIC (confirmed by maintainer WaynePhillipsEA around October 2025). The two NOTE callouts near the top of the page also assert that PtrSafe is required for x64 builds and should be reviewed for accuracy against the current compiler behaviour._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When calling a Win32 API that accepts a `POINT` structure by value, passing two separate **Long** parameters (one for `x`, one for `y`) does not work reliably on x64. On 64-bit targets the first four arguments are passed in registers rather than on the stack, so a two-**Long** layout does not match what the API expects. A cross-bitness workaround is to declare the parameter as **ByVal Currency**: a **Currency** value is 8 bytes --- the same size as a 64-bit **POINT** --- and is passed in a single slot, matching the x64 calling convention. This approach works on both 32-bit and 64-bit twinBASIC targets without conditional compilation. + +_Source threads: 1421815804172701708 · confidence: medium_ +_Date range: 2025-09-30_ +_Reviewer note: Community-sourced workaround (wqweto). Verify that passing ByVal Currency for a POINT-by-value parameter produces the correct register layout on x64 before publishing. Consider whether an illustrative code example would be helpful here._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Overloaded `Declare` statements --- for example, one overload taking `LongPtr` parameters and another taking `String` parameters for the same API --- cannot be disambiguated by the compiler when the declarations appear inside a **Class** or **Form** module. The compiler reports "Unable to disambiguate between overloaded methods" in this case. The same declarations work correctly when placed in a standard **Module**. As a workaround, move overloaded API declarations to a standard module and call them from the class or form. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-12-06_ +_Reviewer note: Reported as GitHub issue #2102. Verify whether it has been fixed before publishing._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> Some Win32 ANSI APIs that accept a null or empty buffer to probe the required size may return a byte count rather than a character count. `GetUserNameA` (in `Advapi32.dll`) is a known case: calling it with a null buffer sets the output length to N\*2 instead of N. The practical remedy is to pre-allocate a buffer large enough for the maximum possible value (for example, UNLEN\+1 = 257 characters for user names) rather than relying on a probe call to size the buffer. The `GetUserNameW` (Unicode) variant and similar ANSI APIs such as `GetComputerNameA` do not exhibit this behavior. + +_Source threads: 1187218188728008785 · confidence: medium_ +_Date range: 2023-12-21 to 2023-12-26_ +_Reviewer note: This is a Windows API quirk, not a twinBASIC-specific behavior. Verify whether this note is within scope for Declare.md (which currently focuses on syntax) or whether it belongs in a different location such as a Win32 API usage guide._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> The `Null` keyword is only valid with **Variant** typed values. Assigning `Null` to a `LongPtr` or any non-Variant numeric type raises run-time error 'Invalid use of Null'. To pass a null pointer to a Win32 API declared with a numeric pointer parameter, use the value `0` (or `ByVal 0` when the parameter is `ByRef`). + +> [!NOTE] +> twinBASIC introduces **vbNullPtr** as a special value that can be passed in place of a typed UDT argument when the underlying Win32 API accepts NULL for that parameter. VB6 required such optional struct arguments to be declared `As Any` and called with `ByVal 0&`; twinBASIC lets the declaration use the real UDT type and the caller substitutes **vbNullPtr** to pass the equivalent of a C NULL pointer. + +```tb +' Win32 declaration using the real UDT type: +Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileW" ( _ + ByVal lpFileName As LongPtr, _ + ByVal dwDesiredAccess As Long, _ + ByVal dwShareMode As Long, _ + lpSecurityAttributes As SECURITY_ATTRIBUTES, _ + ByVal dwCreationDisposition As Long, _ + ByVal dwFlagsAndAttributes As Long, _ + ByVal hTemplateFile As LongPtr) As LongPtr + +' Pass vbNullPtr where the API accepts NULL: +Dim hFile As LongPtr +hFile = CreateFile(StrPtr("C:\test.txt"), GENERIC_READ, FILE_SHARE_READ, _ + vbNullPtr, OPEN_EXISTING, 0, 0) +``` + +### String arguments and DeclareWide + +Win32 API declarations in twinBASIC follow one of three patterns depending on how string arguments are handled: + +- **ANSI variant** (e.g. `MessageBoxA`) --- string parameters declared `As String`; string literals or **String** variables are accepted directly. +- **Wide variant** (e.g. `MessageBoxW`) --- string parameters declared `As LongPtr`; the caller must pass `StrPtr(s)` to obtain the address of the string's character buffer. +- **Alias variant** (e.g. `MessageBox`) --- declared using twinBASIC's `DeclareWide` feature; accepts bare **String** arguments and internally calls the W API. This is the simplest form for Unicode-correct calls. + +Passing a **String** variable where a `LongPtr` is expected results in run-time error 13 (Type Mismatch). + +_Source threads: 1195546112459821176 · confidence: high_ +_Date range: 2024-01-13_ +_Reviewer note: Verify the vbNullPtr feature is documented in the .twin sources. The CreateFile example is illustrative; reviewer should check it compiles correctly with WinDevLib-style declarations. DeclareWide description should be cross-checked against docs/Features/Advanced/API-Declarations._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC (like VB6) cannot consume .NET managed assemblies. Only traditional COM/ActiveX DLLs and standard Win32 DLLs are supported. Importing a .NET assembly through the **Project > References** dialog will add it to the Imported Type Libraries node but its classes will not be available for use. To call into a .NET library from twinBASIC, the library must expose a COM-callable wrapper. + +> [!NOTE] +> +> twinBASIC supports the `CDecl` calling convention natively in **Declare** statements. Append **CDecl** after the procedure name in the declaration to call C-exported functions that use the `cdecl` convention rather than `stdcall`. In VB6, `cdecl` support requires a third-party patch and does not work in the IDE. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. + +_Source threads: 1253786438454214788 · confidence: high_ +_Date range: 2024-06-21 to 2024-06-24_ +_Reviewer note: Two separate notes merged here. (1) The .NET-assembly restriction should be verified against any twinBASIC roadmap items --- a COM-callable-wrapper requirement is the current state as of the Discord thread. (2) The CDecl note references the Features/Advanced/API-Declarations page; confirm the anchor #cdecl-support resolves correctly._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Type libraries that follow the Windows SDK definition closely (such as WinDevLib) declare pointer-to-buffer and pointer-to-UDT parameters as `ByRef ` rather than `ByVal LongPtr`. At the call site, pass the variable or array element directly --- for example, `Data(0)` rather than `VarPtr(Data(0))`. For an optional pointer that should be null, pass `vbNullPtr` (without `ByVal`) rather than `ByVal 0`, which causes a type-coercion error. This differs from declarations that use `ByVal LongPtr` throughout, where `VarPtr()` is needed at every call site. Because twinBASIC allows substituting a `LongPtr` for a UDT argument, a library can retain the real SDK types for all pointer parameters while still accepting a `LongPtr` at the call site when needed. + +_Source threads: 1376962828497326190 · confidence: high_ +_Date range: 2025-05-27 to 2025-05-30_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Declaring an external function parameter as `ByVal Value As String` in a `DeclareWide` declaration does not copy the string at the call site. Passing a **String** `ByVal` is equivalent to passing `StrPtr(s)` --- the runtime passes the raw pointer to the BSTR data. For `DeclareWide` (`LPWSTR` / BSTR) parameters there is no conversion copy at all. This is why `ByVal ... As String` is used to declare parameters that accept `LPWSTR` even though the parameter is conceptually a pointer. +> +> To pass `NULL` to a `DeclareWide` **String** parameter, use `vbNullString` --- not `0&` or `vbNullPtr`. + +_Source threads: 1382372425118781500 · confidence: high_ +_Date range: 2025-06-11_ +_Reviewer note: Verify that this is specifically true of DeclareWide and not all Declare forms. The source (WaynePhillipsEA) is the twinBASIC maintainer, so confidence is high, but it would be worth noting whether ANSI Declare (non-Wide) behaves differently at the call site._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> twinBASIC does not follow the VB6 convention of searching the application's own folder first when loading a DLL at runtime. Instead, it applies a search order that prioritizes system folders. If both the system and the application folder contain a file with the same name, the system copy is loaded rather than the local copy. This is a known difference from VB6 (tracked in GitHub issue [#455](https://github.com/twinbasic/twinbasic/issues/455)). Projects ported from VB6 that ship local copies of DLLs alongside the executable should account for this behavior. + +_Source threads: 1450300236130947143 · confidence: medium_ +_Date range: 2025-12-16_ +_Reviewer note: Verify the GitHub issue number and confirm the search order behavior against the twinBASIC source or issue tracker._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **Passing strings from a VB6 caller into a tB Standard DLL.** When a VB6 program passes a string to a tB Standard DLL function via `StrPtr` (as a `LongPtr`), the tB side cannot simply assign the pointer to a `String` variable. The correct conversion uses `SysReAllocStringW` from `oleaut32.dll`: +> +> ```tb +> Private Declare PtrSafe Function SysReAllocStringW Lib "oleaut32" _ +> Alias "SysReAllocStringW" (ByVal pbstr As LongPtr, ByVal psz As LongPtr) As Long +> +> Public Sub DoSomethingWithAString(ByVal lpString As LongPtr) +> Dim s As String +> SysReAllocStringW VarPtr(s), lpString +> ' s now holds the caller's string +> End Sub +> ``` +> +> The alternative is to declare the parameter `As Variant` on both sides --- VB6 passes a `Variant` holding the string, and tB receives it without needing `StrPtr` or pointer arithmetic at all. Using `SysAllocStringLen` combined with `CopyMemory` is incorrect and causes a crash on DLL unload. + +_Source threads: 1488640456316420182 · confidence: high_ +_Date range: 2026-03-31_ +_Reviewer note: Verify the exact declaration alias for SysReAllocStringW (oleaut32 export name) and confirm the VarPtr usage against the tB runtime. Also verify whether the Variant-parameter alternative works without any special marshalling on the VB6 Declare side._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC supports the **CDecl** calling convention modifier on **Function** and **Sub** declarations. Place **CDecl** between the procedure name and the argument list to mark a procedure as using the C calling convention rather than the default **StdCall**: +> +> ```tb +> Private Function WriteBlock CDecl(ByVal pThis As LongPtr, ByVal pData As LongPtr, ByVal size As Long) As Long +> ``` +> +> This is particularly useful when passing a function pointer to a DLL that expects a `CDecl`-convention callback (for example, a struct containing a function pointer such as `FPDF_FILEWRITE` in pdfium). In VB6, achieving this required a third-party addin; twinBASIC handles it natively. Omitting **CDecl** when the DLL requires it causes a stack imbalance and typically a crash under 32-bit. +> +> **CDecl** is also accepted on `Declare` statements themselves, for DLL exports that use the C calling convention: +> +> ```tb +> Declare PtrSafe Function SomeFunc CDecl Lib "some.dll" (ByVal x As Long) As Long +> ``` + +_Source threads: 1255834190461669446 · confidence: medium_ +_Date range: 2024-06-27 to 2024-07-02_ +_Reviewer note: Verify exact syntax position of CDecl in both the callback-function form and the Declare-statement form against the twinBASIC .twin sources or compiler grammar. The thread confirms the callback form `Function WriteBlock CDecl(...)` works, but the Declare-statement form shown in the second code block is inferred from context (fafalone mentions changing `Lib` to `CDecl Lib` for 32-bit pdfium declares) and may have a different syntax shape. Adjust or split accordingly._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1359525652751192184] + +> [!NOTE] +> +> When declaring undocumented or poorly documented Win32 APIs, C `SIZE_T` parameters must be declared as **LongPtr** rather than **Long**. `SIZE_T` is 4 bytes on 32-bit Windows and 8 bytes on 64-bit Windows; declaring it as **Long** causes incorrect behavior or crashes on 64-bit systems. Similarly, any C `PVOID`, `HANDLE`, or pointer-typed parameter that appears as `Long` in 32-bit code must become **LongPtr** before the declaration is marked **PtrSafe**. + +_Source threads: 1315314732017778808 · confidence: medium_ +_Date range: 2024-11-28 to 2024-12-08_ +_Reviewer note: Merges findings from threads 1311572762413764618 (PtrSafe required for 64-bit builds) and 1315314732017778808 (SIZE_T must be LongPtr). The PtrSafe/LongPtr requirement is already covered by the two existing NOTEs in the page; the SIZE_T detail is the genuinely new information. Verify the SIZE_T claim against the .twin source or Win32 type headers if possible._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808] + +> [!NOTE] +> Pointer-type parameters in `Declare` statements default to **ByRef** if no passing convention is specified, which is correct for output or in-out pointer arguments. For memory-copy APIs such as `RtlMoveMemory` / `CopyMemory`, parameters that carry a raw address (rather than an actual reference to a variable) must be declared **ByVal**. Omitting `ByVal` on such parameters passes the address of the address rather than the address itself, causing silent data corruption --- including misdirected writes --- that may not produce an obvious error but can affect program behavior in ways that are hard to diagnose. + +_Source threads: 1359525652751192184 · confidence: medium_ +_Date range: 2025-04-09_ +_Reviewer note: The original finding came from a UserControl that called CopyMemory/RtlMoveMemory. The note is a general ByVal/ByRef correctness reminder rather than specific to CopyMemory. Consider whether this overlaps with existing Declare documentation on ByRef being the default._ + +--- + +## docs/Reference/Core/Declare.md · see-also + +- [Enhanced API Declarations](../../Features/Advanced/API-Declarations) -- CDecl calling convention support, DeclareWide, variadic arguments, and other Declare enhancements + +_Source threads: 1309934209338703882 · confidence: high_ +_Date range: 2024-11-23_ +_Reviewer note: The CDecl native support finding (twinBASIC eliminates the need for VBCDeclFix) is already fully documented on the Enhanced API Declarations feature page. This See Also entry surfaces that page from the Declare reference page, where users are most likely to look. Verify the relative URL resolves correctly from /tB/Core/Declare to /Features/Advanced/API-Declarations._ + +--- + +## docs/Reference/Core/Deftype.md · after-remarks [DUPLICATE? -- see also thread 1186715657568534628] + +> [!NOTE] +> twinBASIC applies **Def**_type_ inference to typeless **constants**, unlike VB6 and VBA. With `DefObj A-Z` active, a constant declared without an explicit type --- such as `Private Const MySize = 8` --- is inferred as **Object**. Using that constant where a numeric value is required, for example as an array bound, fails at compile time with "error occurred in evaluating constant expression". The fix is to declare the constant with an explicit type: `Private Const MySize As Long = 8`. +> +> The same inference applies to typeless **Optional** parameters. With `DefObj A-Z` active, `Optional ByVal TimeOutMs = 30000` causes a compile error because the numeric default cannot be converted to **Object** without silently discarding the value. twinBASIC treats this as an error rather than silently dropping the default (the VB6 behavior). The fix is to type the parameter explicitly: `Optional ByVal TimeOutMs As Long = 30000`. +> +> Both behaviors were confirmed as intended by BETA 207 (the constant case was previously a bug and was fixed in that release; the Optional-parameter error is by design). + +_Source threads: 1052611285310775406 · confidence: high_ +_Date range: 2022-12-14 to 2022-12-17_ + +--- + +## docs/Reference/Core/Deftype.md · after-remarks [DUPLICATE? -- see also thread 1052611285310775406] + +> [!NOTE] +> twinBASIC does not fully support type-declaration suffix characters (such as `&` for **Long**, `!` for **Single**, and so on) in all expression contexts. When a variable name with a `&` suffix appears inside a string-concatenation expression, the compiler misreads the `&` suffix as the string-concatenation operator and reports 'Two operators encountered, without an operand between them'. VB6 handles this case correctly. Avoid mixing type-declaration suffix characters with concatenation expressions; use explicit **As** *type* declarations instead. +> +> A related parser limitation: twinBASIC does not auto-correct the reversed comparison operator `=<` to `<=` as VB6 did --- it reports the same misleading error. + +_Source threads: 1186715657568534628 · confidence: medium_ +_Date range: 2023-12-19_ +_Reviewer note: This finding is about type-declaration suffix characters (e.g. example_long&) rather than the Def* statements the Deftype page covers. The Deftype page is the closest match in the page index for implicit typing. Verify against the twinBASIC compiler source or a current build to confirm whether this has been fixed before publishing._ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1462084095428526296, 1373697250865451099, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> In releases before BETA 786, using a **Delegate** type as a parameter type in an **Interface** defined in an external package caused compilation errors (TB5079 'Unrecognized datatype symbol', TB5004, and TB5000) when a consumer project attempted to implement that interface. This was a compiler bug --- not a language restriction --- and was reported as fixed in BETA 786. + +_Source threads: 1293249305355747409 · confidence: high_ +_Date range: 2024-10-08 to 2025-06-02_ +_Reviewer note: Verify that BETA 786 is the correct fix version and that no further regression has been introduced before the current release._ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1373697250865451099, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> When a delegate-typed variable is declared as a member of a class (or `[AppObject]` class), calling it with the normal invocation syntax --- `delegateVar(arg1, arg2)` --- raises *Unexpected call arguments* because the compiler treats the access as a property get rather than a callable invocation. The workaround is to use an extra set of parentheses: +> +> ```tb +> delegateVar()(arg1, arg2) +> ``` +> +> Delegate variables declared at module level (not inside a class) are not affected. The bug was a regression introduced around BETA 929 and was fixed in BETA 954. + +_Source threads: 1462084095428526296 · confidence: high_ +_Date range: 2026-01-17_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> `As Any` is not permitted as the return type of a **Delegate Function** declaration. Using it produces compiler error TB5079 (*Unrecognised datatype symbol Any*). `Any` is valid as a parameter type (`ByRef param As Any`) but not as the return type. The standard workaround is to change the delegate to return **Boolean** and add a `ByRef` output parameter typed `As Any` to carry the return value. +> +> Assigning an **AddressOf** expression to a delegate array may also produce warning TB0026 (*Implicit conversion to delegate type. Use CType(of) to explicitly convert the delegate to a different type*). This warning is expected and the code operates correctly. + +_Source threads: 1373697250865451099 · confidence: high_ +_Date range: 2025-05-18 to 2025-05-21_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1373697250865451099, 1363971952691118140] + +> [!NOTE] +> Delegate support was added in twinBASIC beta 617. Opening a project that uses **Delegate** syntax in an earlier version produces a large number of compile errors. Users on an older build must upgrade to beta 617 or later before working with such projects. + +_Source threads: 1306091764687573062 · confidence: high_ +_Date range: 2024-11-13_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1373697250865451099, 1306091764687573062] + +> [!NOTE] +> Delegate-based thread spawning is exclusive to twinBASIC --- VB6 and standard VBA have no equivalent. A concrete use case is offloading per-frame decode work in an animated-GIF player: rather than decoding each frame serially in a `Timer` handler on the UI thread, instantiate a delegate for the per-frame callback and let each frame complete on its own thread via `CreateThread` or a similar API, passing `AddressOf` *DecodeFrame* as the thread entry point. See [Multithreading](../../Features/Advanced/Multithreading) for a working `CreateThread` example. + +_Source threads: 1363971952691118140 · confidence: medium_ +_Date range: 2025-04-21 to 2025-07-22_ + +--- + +## docs/Reference/Core/Delegate.md · example + +An array of delegates provides a clean replacement for VB6's computed **GoSub** pattern. Declare a delegate type, fill an array with **AddressOf** references, then call a specific delegate by index: + +```tb +Private Delegate Function Operation (ByVal A As Long, ByVal B As Long) As Long + +Public Function Addition(ByVal A As Long, ByVal B As Long) As Long + Return A + B +End Function + +Public Function Subtraction(ByVal A As Long, ByVal B As Long) As Long + Return A - B +End Function + +Private Sub Command1_Click() + Dim ops(0 To 1) As Operation + ops(0) = AddressOf Addition + ops(1) = AddressOf Subtraction + + Dim index As Long + index = 0 ' select which operation to run + Dim result As Long + result = ops(index)(10, 3) ' calls ops(0), i.e. Addition + MsgBox result ' displays 13 +End Sub +``` + +> [!NOTE] +> When invoking a delegate stored in an array, the call-site parentheses are required: `ops(index)(arg1, arg2)`. Writing `ops(index)` without trailing parentheses reads the function pointer value but does not call it; the IDE may also report warning TB5086 (*Expression is neither used nor assigned*) at that point. +> +> As of January 2026, delegates with no parameters invoked from an array are reported as broken. Parameterized delegates work correctly. The [**On...GoSub**](On-GoSub) statement remains available as an alternative for index-based dispatch. + +_Source threads: 1463264948942672172 · confidence: high_ +_Date range: 2026-01-20 to 2026-01-22_ +_Reviewer note: Add as an additional example block after the existing three examples in Delegate.md. Verify whether the parameterless-delegate-from-array bug is still present in the current release; update or remove the note if it has been fixed._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> In a multi-variable declaration, each variable requires its own **As** *type* clause. Writing `Dim a, b, c As Integer` declares only `c` as **Integer**; `a` and `b` receive no explicit type and default to **Variant**. This matches VB6 and VBA behavior. twinBASIC reports a warning for each variable whose type was not explicitly declared. To declare multiple variables of the same type, use a separate **As** clause for each: `Dim a As Integer, b As Integer, c As Integer`. + +_Source threads: 1058362981156671629 · confidence: high_ +_Date range: 2022-12-30 to 2022-12-31_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> An array variable used in an expression without a subscript index --- for example, writing `He` instead of `He(i)` when `He` is declared `As String` array --- is a type error. Prior to BETA 313, the compiler accepted such code silently and then crashed at the code-generation stage with an access violation before the program started. From BETA 313 onward, the compiler correctly reports this as a compile-time error. + +_Source threads: 1099362561482301523 · confidence: high_ +_Date range: 2023-05-24_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> Inline initialization (`= *expression*`) is not permitted when the variable name includes the array-suffix `()`. For example, `Dim myA() As Variant = Array(1,2,3)` produces a compiler error. Two alternatives exist: +> +> 1. Split the declaration and assignment: `Dim myA() As Variant` on one line, then `myA() = Array(1,2,3)` on the next. +> 2. Declare without the `()` suffix: `Dim myA As Variant = Array(1,2,3)`. +> +> These two forms are not interchangeable. `myA() As Variant` declares a typed SAFEARRAY of **Variant** elements. `myA As Variant` declares a scalar **Variant** that can hold a SAFEARRAY of any element type. Only the bare-**Variant** form supports inline initialization. + +_Source threads: 1172828722081058897 · confidence: medium_ +_Date range: 2023-11-11_ +_Reviewer note: Verify against current compiler behavior: does the restriction on inline initialization for array-suffix declarations still apply, and is the distinction between `myA() As Variant` (typed SAFEARRAY) and `myA As Variant` (scalar Variant wrapping a SAFEARRAY) accurate?_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> In VB6 and VBA, assigning a `Variant()` array variable directly to a typed array variable (for example, `myLongArray = myVariantArray`) is a compile-time error: *Can't assign to array*. In twinBASIC through at least BETA 495, this assignment compiles and runs without error, but produces incorrect results: each element of the typed array is set to the **VarType** integer of the source element type rather than the element value itself (for example, `3` instead of the actual **Long** value). The same problem affects **String** arrays. Code migrated from VB6 that relied on the compile-time error to catch this mistake will silently produce wrong output. Until the compiler is updated to match VB6 behaviour, copy elements individually instead of assigning the array variable directly. + +_Source threads: 1225747919072923678 · confidence: high_ +_Date range: 2024-04-05 to 2024-04-07_ +_Reviewer note: Confirm whether this bug has been fixed in a release after BETA 495. If fixed, update the note to name the fixing release and convert to past-tense historical context or remove if already documented elsewhere._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> When an array bound is computed from unqualified integer literals, the compiler evaluates the expression using 16-bit **Integer** arithmetic -- the same behaviour as VB6. Expressions whose result exceeds 32767 silently overflow or raise a run-time error. To force **Long** arithmetic, append the `&` type-declaration character to at least one literal in the expression: +> +> ```tb +> Dim Pixels(640& * 480& - 1) As Long ' Long arithmetic; no overflow +> ' Dim Pixels(640 * 480 - 1) As Long ' Integer arithmetic; overflows +> ``` +> +> This applies wherever a constant expression is used for an array bound or index -- including [**ReDim**](ReDim) statements. + +_Source threads: 1434184886280786001 · confidence: high_ +_Date range: 2025-11-01_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> twinBASIC extends **WithEvents** to support dynamic arrays: `Private WithEvents myObjs() As MyClass` is valid and the array can be resized with [**ReDim**](ReDim). Fixed-length array declarations such as `Private WithEvents myObjs(2) As MyClass` are not supported. When any element raises an event, the corresponding handler (for example `Private Sub myObjs_MyEvent()`) fires but receives no index parameter --- there is no built-in way to identify which element was the source. To work around this, include an explicit index in the event's parameter list on the raising side, or wrap each object in a separate class that carries identity information. + +_Source threads: 1326158767561248768 · confidence: high_ +_Date range: 2025-01-07 to 2025-01-08_ +_Reviewer note: Verify against the twinBASIC compiler source or a live test: confirm that (a) dynamic WithEvents arrays compile and function correctly, and (b) fixed-length WithEvents arrays crash the IDE as reported, before publishing._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> `Dim obj As New MyClass` uses lazy instantiation --- the object is not created until the variable is first accessed. The inline-initialization form `Dim obj As MyClass = New MyClass(args)` creates the object immediately at the point of declaration. These two forms are not interchangeable when the timing of construction matters. + +When using parameterized constructors (classes marked `[COMCreatable(False)]` with a constructor that takes arguments), `Dim obj As MyClass = New MyClass(args)` requires the class name twice. The short form `Dim obj As Any = New MyClass(args)` avoids the repetition: `As Any` causes the compiler to infer the actual type from the right-hand side and triggers immediate construction, not lazy instantiation. + +_Source threads: 1468334726770200800 · confidence: high_ +_Date range: 2026-02-03 to 2026-02-08_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1402639656574648331] + +> [!NOTE] +> In twinBASIC, a **Dim** statement with an inline initializer (e.g. `Dim id As Long = sqlite3_column_int(stmt, 0)`) is valid inside a loop body. On each iteration twinBASIC treats the declaration as a fresh assignment rather than a re-declaration error. Classic VBA and VB6 require **Dim** to appear at the top of the procedure scope and reject any re-declaration within the same scope. The semantics are equivalent to declaring the variable once at procedure scope and assigning it on each pass through the loop, but the inline form is accepted twinBASIC syntax. + +_Source threads: 1384557209232478240 · confidence: medium_ +_Date range: 2025-06-20_ +_Reviewer note: The thread_path is from the debugging thread but the Dim finding came from a code snippet in that thread; the actual thread path for this finding is wisdom/data/threads/questions/1384557209232478240. Verify against the twinBASIC compiler spec or .twin source that duplicate Dim in a loop truly does not error --- the Dim.md page already documents the initializer form but does not address loop-body re-declaration. If unverified, consider lowering confidence or omitting._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240] + +> [!NOTE] +> When multiple variable names precede a single **As** *type* clause --- `Dim x, y, z As Integer` --- only the last name (`z`) receives the declared type. The earlier names (`x` and `y`) are implicitly typed as **Variant**. This matches VB6 behavior and is preserved for compatibility. twinBASIC issues a compiler warning on each implicitly typed name in such a list; the warning does not prevent compilation. To give all three variables the same type, use a separate **As** clause for each: `Dim x As Integer, y As Integer, z As Integer`. + +> [!NOTE] +> `Dim x As Any = 1&` infers the variable's concrete type from the initializer --- in this case **Long** --- rather than declaring it as **Variant**. After that declaration, `x = "test"` is a type error. `As Any` without an initializer behaves differently; it is not a synonym for `As Variant`. See [Type Inference](../../Features/Language/Type-Inference). + +_Source threads: 1402639656574648331 · confidence: high_ +_Date range: 2025-08-06 to 2025-09-01_ +_Reviewer note: Two separate behavioral clarifications about Dim are merged here. Verify the As Any without initializer behavior against the .twin source or compiler tests: the finding says it "behaves differently" but does not fully specify what it does in that case._ + +--- + +## docs/Reference/Core/Dim.md · example + +This example shows the short form for constructing objects with parameterized constructors, avoiding repetition of the class name. + +```tb +' Full form — class name appears twice. +Dim conn As MyConnection = New MyConnection("localhost", 5432) + +' Short form using As Any — type is inferred from the right-hand side. +' Constructs immediately (not lazily). +Dim conn As Any = New MyConnection("localhost", 5432) +``` + +_Source threads: 1468334726770200800 · confidence: high_ +_Date range: 2026-02-05 to 2026-02-08_ + +--- + +## docs/Reference/Core/Divide.md · after-remarks + +> [!NOTE] +> When both operands are compile-time constants (numeric literals), the compiler detects division by zero during constant folding and emits a runtime error call instead of performing the FPU operation. As a result, `On Error Resume Next` catches the error before the result is written to the variable, leaving the variable at its default value (0 for **Double**) rather than the IEEE 754 special value. To obtain the correct IEEE 754 result, store the divisor in a variable so constant folding does not apply: +> +> ```tb +> Dim zero As Double = 0 +> Dim posInf As Double +> On Error Resume Next +> posInf = 1 / zero ' posInf receives +Infinity +> On Error GoTo 0 +> ``` +> +> This was confirmed as a bug and fixed in twinBASIC BETA 375. + +_Source threads: 1135331052928389260 · confidence: high_ +_Date range: 2023-07-30 to 2023-08-14_ + +--- + +## docs/Reference/Core/End.md · after-remarks + +> [!NOTE] +> In some twinBASIC builds, the bare **End** statement does not terminate execution as expected --- the program may continue running or re-show closed forms rather than stopping. Use `Unload ` on each open form and let the program exit naturally when no forms remain, or close the last form from its own `QueryUnload` or `Unload` handler. This behaviour has been reported as a known issue. + +_Source threads: 1109123342537085009 · confidence: high_ +_Date range: 2023-05-19_ +_Reviewer note: Verify whether this issue is still present in current twinBASIC builds and whether a specific bug number or fix version can be cited. The Discord report was from BETA 308 (May 2023). If fixed, remove or qualify the note with a fixed-in version._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> The twinBASIC IDE automatically reformats **Enum** blocks when the cursor enters or scrolls past them. Extra spaces added before `=` to align member values across lines are removed, and leading zeros added after `&H` in hexadecimal literals (for example, `&H0001` written to match the width of `&H0100`) are also removed. Manual visual alignment of enum members is not preserved. + +_Source threads: 1085419449387073627 · confidence: medium_ +_Date range: 2023-03-15_ +_Reviewer note: Verify this auto-format behavior is still present in current IDE builds and that it applies on scroll as well as on direct edit. The finding is from March 2023 and IDE behavior may have changed._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> Member names that require bracket escaping --- names containing spaces, hyphens, or other characters that are not valid in an unquoted identifier (for example, `[8 Bytes]`, `[bytes-8]`, `[24_Bytes]`) --- are accepted by the compiler. Prior to BETA 356, IntelliSense completions inserted such names without the enclosing square brackets, producing code that did not compile. The fix ships in BETA 356; on earlier builds, type the brackets manually when using a bracket-escaped member name. + +_Source threads: 1127957663662219364 · confidence: high_ +_Date range: 2023-07-10 to 2023-07-12_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> twinBASIC reports a compile-time ambiguity error when two or more UserControls in the same project declare an **Enum** with overlapping member names. VB6 permitted this pattern without error. Code ported from VB6 projects that relies on same-named enum members in separate UserControl files must rename the conflicting members (or the enumerations themselves) to be unique across the project. + +_Source threads: 1128708250875998358 · confidence: high_ +_Date range: 2023-07-12_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> If an **Enum** type and a **Sub** or **Function** share the same name within the same project, twinBASIC may compile the project without a clear error but the resulting program can crash or behave erratically at run time. Renaming one of the conflicting identifiers resolves the problem. + +_Source threads: 1141408151762112583 · confidence: medium_ +_Date range: 2023-08-16_ +_Reviewer note: Verify whether this ambiguity is compiler-detected in current twinBASIC builds (the report is from August 2023 and the compiler may have added a diagnostic since then). If a clear compile-time error is now raised, this note is not needed or should be reworded to reflect the current behavior._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> Declaring a `String` variable and an `Enum` with the same identifier in the same module does not produce a compile-time diagnostic. The name clash is only detected at run time when the conflicting name is accessed. Projects with this pattern can appear to build cleanly but fail unexpectedly at run time. + +_Source threads: 1303048284633432115 · confidence: medium_ +_Date range: 2024-11-04 to 2024-11-06_ +_Reviewer note: Verify whether this missing diagnostic has been addressed in a later compiler release. If fixed, the note should state the fix version or be removed._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> +> Assigning `2 ^ 31` to an **Enum** member raises an overflow error. The `^` operator returns a **Double**, and the resulting value 2 147 483 648 lies outside the range of a signed **Long** (the underlying type of every **Enum** member). Use the hex literal `&H80000000&` instead to represent the most-significant bit of a **Long** flag. This matches VBA and VB6 behavior. + +_Source threads: 1455873650095030384 · confidence: high_ +_Date range: 2025-12-31_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> **Enum values are 32-bit integers.** This is a requirement of the COM specification, which mandates that enum types are 32-bit for interoperability with non-twinBASIC COM consumers. Values outside the signed 32-bit range are not supported in an **Enum** declaration. + +> [!NOTE] +> **Hex literal type-suffix workaround.** The `[IgnoreWarnings]` attribute is not valid on individual enum member lines. When a hex literal assigned to an enum member triggers the "hex misuse" compiler warning (for example, `&HFFFF` used where the bit pattern represents a negative **Long**), append the **Integer** type suffix to suppress it: `&HFFFF%`. twinBASIC treats the type-hinted literal as an **Integer** and stores the correct bit pattern without raising the warning. + +_Source threads: 1141006440270676070 · confidence: high_ +_Date range: 2023-08-15 to 2023-08-31_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1385161863519670332] + +> [!NOTE] +> **Member initializers and built-in functions.** Enum member values may call built-in functions such as `Asc`, but not user-defined functions. Calling a custom function inside an Enum member expression is a compiler error. + +> [!NOTE] +> **`[BitFlags]` attribute.** Decorating an **Enum** with `[BitFlags]` (see [Attributes](../Attributes.md#bitflags)) causes members with no explicit value to be assigned successive powers of two (1, 2, 4, 8, ...) automatically. Once any member is given an explicit value, every subsequent member must also carry an explicit value; mixing explicit and auto-assigned values after the first explicit entry is a compiler error. + +> [!NOTE] +> **Compiler warning TB0019.** twinBASIC raises warning TB0019 when a value of one enum type is assigned to a variable of a different enum type. The warning is disabled (IGNORE) by default because enabling it on imported VB6 or VBA code would produce a large number of warnings. Enable TB0019 in the project settings when stricter enum-type checking is wanted. + +_Source threads: 1381902597056757821 · confidence: high_ +_Date range: 2025-06-10 to 2025-06-11_ +_Reviewer note: Verify the final attribute name ([BitFlags] vs [Flags]) against the Attributes page and the .twin source for Core/Enum. The thread indicated the name was still under discussion as of beta 803. The Enum page already links to Attributes#flags --- confirm whether the canonical name is [Flags] or [BitFlags] and update the link anchor accordingly._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821] + +> [!NOTE] +> All enum members occupy exactly 4 bytes (**Long**) in both 32-bit and 64-bit builds. twinBASIC is COM-based, and the typelib format does not support enum members of other sizes for exported types such as ActiveX DLLs and controls. Future versions may allow enums with other underlying integer types for internal use, but such types cannot be placed in ActiveX DLLs or controls. + +_Source threads: 1385161863519670332 · confidence: high_ +_Date range: 2025-06-19_ + +--- + +## docs/Reference/Core/Event.md · after-remarks + +> [!NOTE] +> In twinBASIC before BETA 717, events declared with `As Object` parameters in an ActiveX DLL were emitted into the type library as `IDispatch**` instead of retaining `Object` semantics. VB6 clients consuming such a DLL raised "Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic". The fix landed in BETA 717. A secondary regression in BETA 717 caused `As IUnknown` event parameters to appear as `As 0` / `As Unknown` in VB6 object browsers; that regression was corrected in BETA 718. + +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-03-20 to 2025-03-21_ + +--- + +## docs/Reference/Core/Exit.md · after-remarks + +> [!NOTE] +> The form `Exit For` always exits the immediately enclosing `For` loop. Naming a specific loop variable in the exit statement (e.g. `Exit For outerVar`) is not valid syntax in current twinBASIC. The standard workaround for breaking out of an outer loop from an inner one is to place a [**GoTo**](GoTo) label immediately after the outer loop, or to extract the inner loop body into a separate **Sub** and use **Exit Sub**. + +_Source threads: 1058360505904934923 · confidence: high_ +_Date range: 2022-12-30 to 2023-01-03_ +_Reviewer note: The Discord thread dates from January 2023. Verify whether named-loop-variable Exit For has been added in a later twinBASIC release before publishing._ + +--- + +## docs/Reference/Core/Exponent.md · after-remarks + +> [!NOTE] +> +> twinBASIC IDE BETA 423 contained a bug where the **^** operator reversed its operands when the exponent was a variable rather than a literal constant. For example, `15 ^ (TreeLevel - 1)` with `TreeLevel = 3` returned 32768 (2^15) instead of 225 (15^2). This bug was fixed in a subsequent release. If unexpected exponentiation results occur, verify that the twinBASIC IDE is up to date. + +_Source threads: 1189197083534708818 · confidence: medium_ +_Date range: 2023-12-26_ +_Reviewer note: The bug was reported in BETA 423 and expected to be fixed soon after. Confirm the fix version before publishing, or consider omitting this note if it is no longer relevant to any supported release._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1500134322588618753, 1260650408179667046, 1478776593890410558] + +> [!NOTE] +> +> Iterating over a **Byte** array with **For Each** acquires a runtime lock on the array. If the enclosing function exits before the loop completes --- for example via a **Return** statement --- the lock may not be cleared. A subsequent call that passes the same array to another procedure can then raise runtime error "Memory is locked". The workaround is to replace the **For Each** loop with an index-based **For** loop: +> +> ```tb +> For i = 0 To UBound(arr) +> ' process arr(i) +> Next +> ``` +> +> This avoids the array-locking mechanism entirely. + +_Source threads: 1456208651550331067 · confidence: medium_ +_Date range: 2026-01-01_ +_Reviewer note: Whether the failure to unlock on early exit is a known bug or VB6-compatible behavior was unresolved in the source thread. Verify against current runtime behavior before publishing._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1260650408179667046, 1478776593890410558] + +> [!NOTE] +> Iterating a typed array (for example, `Dim pts() As PointXY`) with **For Each** after passing the array through one or more method calls may raise an 'Object required' error when methods on the loop control variable are accessed. The same array enumerated with a numeric index loop works correctly. Workarounds: (1) declare the array as `Variant` instead of a specific class type; (2) use an index-based **For...Next** loop; (3) cast the loop variable to the interface that exposes the required method. + +_Source threads: 1500134322588618753 · confidence: medium_ +_Date range: 2026-05-02_ +_Reviewer note: Observed in Beta 979 on Windows 11; a minimal reproduction was not produced. Verify whether this is still present in later builds before publishing._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1500134322588618753, 1478776593890410558] + +> [!NOTE] +> When a breakpoint is set inside a custom `IEnumVARIANT.Next` implementation, the IDE Variables pane automatically enumerates the collection to populate its display. This triggers another call to `Next` before the previous call returns, causing the debugger to appear to enter an endless loop and the IDE to become unresponsive. The workaround is to collapse the Variables pane node for the collection before reaching the breakpoint in `Next`. With that node collapsed, stepping through `Next` with **F11** works normally. + +_Source threads: 1260650408179667046 · confidence: high_ +_Date range: 2024-07-10_ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1500134322588618753, 1260650408179667046] + +> [!NOTE] +> To make a custom class support **For Each...Next** enumeration, implement the `IEnumVARIANT` COM interface directly in the class. Unlike VB6, no assembly thunks or external helpers are required --- twinBASIC allows the interface to be declared and implemented in plain source code: +> +> ```tb +> [InterfaceId("00020404-0000-0000-C000-000000000046")] +> Interface IEnumVARIANT +> Extends stdole.IUnknown +> Sub Next(ByVal ipRequestedCount As Long, ByRef iopReturnedItems As Variant, ByRef opReturnedCount As Long) +> Sub Skip(ByVal ipRequestedCount As Long) +> Sub Reset() +> Sub Clone(ppenum As IEnumVARIANT) +> End Interface +> ``` +> +> A simpler alternative when the data is already stored in a `Collection`: expose a `[_NewEnum]` property that returns the inner Collection's enumerator, and **For Each** works without implementing `IEnumVARIANT` directly. + +_Source threads: 1478776593890410558 · confidence: high_ +_Date range: 2026-03-04_ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1382180932131229716, 1468853059148054752] + +> [!NOTE] +> Inline loop-variable declaration (`For myIndex As Long = 1 To 25`) is a twinBASIC extension. VBA and VB6 require the variable to be declared separately with **Dim** before the **For** statement. + +_Source threads: 1346588557325762621 · confidence: high_ +_Date range: 2025-03-04 to 2025-03-13_ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1346588557325762621, 1468853059148054752] + +> [!NOTE] +> +> **Driver projects and integer overflow checking.** In twinBASIC driver projects compiled without the standard runtime, any loop construct --- including **For...Next**, **Do...Loop**, **While...Wend**, and **Do...Loop Until** --- causes the compiler to emit imports for `KERNEL32.dll!HeapAlloc`, several `OLEAUT32.dll` BSTR marshalling and variant conversion functions (`BSTR_UserMarshal`, `BSTR_UserUnmarshal`, `BSTR_UserSize`, `SysAddRefString`, `VarBstrCmp`, `VarDateFromI2`, `VarDateFromI4`), `USER32.dll!PostMessageW`, `USER32.dll!MessageBoxW`, and additional `KERNEL32.dll` helpers (`FormatMessageW`, `lstrlenW`, `VirtualQuery`, `Sleep`). These come from the integer overflow checking machinery that the compiler emits around loop counter arithmetic. +> +> Apply `[IntegerOverflowChecks(False)]` to the procedure containing the loop to suppress these imports. For driver projects it is advisable to set this attribute project-wide. + +_Source threads: 1382180932131229716 · confidence: high_ +_Date range: 2025-06-11 to 2025-06-25_ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1346588557325762621, 1382180932131229716] + +> [!IMPORTANT] +> +> When compiling a kernel-mode driver, any routine that contains a loop generates calls to user-mode DLLs (`KERNEL32.dll`, `OLEAUT32.dll`, `USER32.dll`) unless the `[IntegerOverflowChecks(False)]` attribute is applied to that routine. The driver verifier flags these calls as invalid for kernel code, which prevents the driver from loading. Apply `[IntegerOverflowChecks(False)]` to every routine in the driver module, or set integer overflow checking off at the project level. This requirement applies even to routines with empty loop bodies. + +_Source threads: 1468853059148054752 · confidence: high_ +_Date range: 2026-02-05_ +_Reviewer note: Verify that [IntegerOverflowChecks(False)] is the correct attribute syntax and that it can be applied project-wide via a project setting in twinBASIC._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1371107177791623208, 1406533475548401664, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> Indirect recursion --- where function A calls function B, which calls function A again --- causes the same stack exhaustion as direct self-calls, but twinBASIC may surface it as a blue **Native Exception** dialog rather than a recognizable stack-overflow error. When an unexpected native exception appears shortly after entering a method, check whether any function in the call chain eventually calls back into a function that is already on the call stack. + +_Source threads: 1207082349364252743 · confidence: medium_ +_Date range: 2024-02-13 to 2024-02-15_ +_Reviewer note: Finding targets VB package (symbol null); placed on Core/Function because that page already discusses recursion and stack overflow. Verify that the native-exception behavior described is reproducible and consistent across twinBASIC builds before publishing._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1406533475548401664, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> When a **Function** returns an array type, writing `FunctionName(i) = value` inside the function body does **not** assign to element *i* of the return value. twinBASIC parses `FunctionName(i)` as a recursive call to the function with `i` as an argument, causing infinite recursion that exhausts the call stack. Unlike VB6, twinBASIC does not reject this syntax at compile time. +> +> The correct pattern is to declare a local variable, build the array into it, and return the variable: +> +> ```tb +> Function GetKeys() As String() +> Dim retVal() As String +> ReDim retVal(0 To 2) +> retVal(0) = "alpha" +> retVal(1) = "beta" +> retVal(2) = "gamma" +> Return retVal +> End Function +> ``` +> +> Using `Return retVal` is the recommended form because it removes all ambiguity. Assigning `GetKeys = retVal` is also correct. + +_Source threads: 1371107177791623208 · confidence: high_ +_Date range: 2025-05-11 to 2025-05-14_ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> When the result assignment (`FunctionName = expression`) is the last line of code in a **Function** procedure, twinBASIC performs a move-pointer (MovePtr) optimization: instead of copying the heap-allocated value, the compiler transfers ownership of the pointer, avoiding the copy. This optimization applies most visibly to arrays and other heap-allocated types. It does not apply to **String** return values. + +_Source threads: 1406533475548401664 · confidence: high_ +_Date range: 2025-08-17 to 2025-08-23_ +_Reviewer note: Verify against compiler source or release notes that MovePtr optimization applies in twinBASIC as described. The finding was confirmed by WaynePhillipsEA but the string exclusion note comes from a secondary observation. Place before the ### Example section._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1406533475548401664, 1363566343353532446] + +> [!NOTE] +> twinBASIC emits diagnostic TB0003 ('Datatype has not been explicitly declared so will default to Variant') when a **Function** declaration omits the **As** *type* return-type clause. This is a warning, not a compilation error --- the function compiles and runs, returning a **Variant**. To suppress the warning, either add the return type, convert the procedure to a **Sub** if no value is returned, or adjust which warnings are reported in the project settings panel in the IDE. + +_Source threads: 1325528980006113280 · confidence: high_ +_Date range: 2025-01-05_ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1406533475548401664, 1325528980006113280] + +> [!NOTE] +> VB6 and VBA cap the number of parameters per procedure at 60. twinBASIC does not enforce this limit. Procedures with 150 or more parameters compile and run without error. The error message "too many arguments" indicates a mismatch between the call site and the declaration --- not a hard compiler limit. + +_Source threads: 1363566343353532446 · confidence: high_ +_Date range: 2025-04-20_ + +--- + +## docs/Reference/Core/Get.md · after-remarks + +> [!NOTE] +> In twinBASIC BETA 317, reading a UDT from a binary file with **Get** produced incorrect data when the UDT contained a dynamic array member. The array elements were read with values far outside the expected range. This was a bug in the **Get** statement's handling of dynamic arrays embedded in UDTs; it was fixed in BETA 320. + +_Source threads: 1112631246338662420 · confidence: high_ +_Date range: 2023-05-30_ +_Reviewer note: Confirm the fix release number (BETA 320) against the changelog before publishing. The note is placed after the existing UDT/dynamic-array bullet in the Random-mode rules section._ + +--- + +## docs/Reference/Core/GoSub-Return.md · after-remarks [DUPLICATE? -- see also thread 1457034604769706168] + +> [!NOTE] +> In twinBASIC, a **Return** statement that can be reached without a prior **GoSub** in the execution path is a compilation error. In VB6 this was a runtime error. Code imported from VB6 projects may fail to compile if a **Return** is reachable without a corresponding **GoSub**---for example, when the **GoSub** is commented out or inside a conditional branch that never executes before **Return**. + +_Source threads: 1324800932869046312 · confidence: high_ +_Date range: 2025-01-03 to 2025-01-06_ + +--- + +## docs/Reference/Core/GoSub-Return.md · after-remarks [DUPLICATE? -- see also thread 1324800932869046312] + +> [!NOTE] +> +> **GoSub** has lower call overhead than a separate **Sub** or **Function** call, because it does not create a new stack frame. The tradeoff is readability: code structured around **GoSub** labels is harder to follow than code split into named helper procedures. For performance-critical inner loops where a helper call is measurably slow, **GoSub** is worth considering; in all other cases a separate procedure is preferred for clarity. + +_Source threads: 1457034604769706168 · confidence: medium_ +_Date range: 2026-01-04_ +_Reviewer note: The existing page carries a [!TIP] recommending separate callable procedures. This note is compatible but subtly contradicts the tip's implied framing. Verify the performance claim against twinBASIC compiler output or a maintainer statement before publishing._ + +--- + +## docs/Reference/Core/GoTo.md · after-remarks + +> [!NOTE] +> In the twinBASIC IDE, pressing **F12** with the cursor on a label name invokes **Go to Definition** and navigates to the label's declaration. Right-clicking the label and choosing **Go to Definition** from the context menu has the same effect. + +_Source threads: 1466015542014902294 · confidence: high_ +_Date range: 2026-01-28_ + +--- + +## docs/Reference/Core/Handles.md · after-remarks + +> [!NOTE] +> When a `Handles` clause references an event declared on an interface that is introduced via `Implements`, IntelliSense may show incorrect or irrelevant completions for the event target. The issue is specific to the `Implements` + `Handles` combination; `Handles` clauses on non-interface event sources are unaffected. + +_Source threads: 1086882000025178112 · confidence: medium_ +_Date range: 2023-03-19 to 2023-03-28_ +_Reviewer note: Reported for IExplorerBrowserEvents used via Implements. Verify whether this is still reproducible in a current twinBASIC build before publishing._ + +--- + +## docs/Reference/Core/If-Then-Else.md · after-remarks + +> [!NOTE] +> A colon placed immediately after `Then` --- as in `If Then:` --- causes the compiler to treat the statement as a completed single-line **If**, not as a block **If** with a colon separator. Any `ElseIf`, `Else`, or `End If` on a following line then produces a compile error. Remove the trailing colon when the block form is intended: write `If Then` without it. twinBASIC catches this at compile time; VB6 only fails at run time. + +_Source threads: 1389259463428542564 · confidence: medium_ +_Date range: 2025-06-30_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> twinBASIC does not support `PreserveSig` when implementing COM interfaces. Interface members declared as functions (returning a value directly rather than via HRESULT) are handled the VB6 way: the compiler generates a `Sub` stub and drops the return value. This matches classic VBA behavior. The auto-generated stub omitted parameter names in versions before BETA 240 (producing invalid syntax such as `Private Sub IOleWindow_GetWindow(As LongPtr) Implements ...`); that bug was fixed in BETA 240. + +_Source threads: 1066331884382072912 · confidence: high_ +_Date range: 2023-01-21 to 2023-02-03_ +_Reviewer note: Verify the exact BETA 240 fix details against the twinBASIC changelog or .twin source if possible. The PreserveSig behavior is consistent with VB6 and is confirmed, but the precise build number of the fix should be double-checked._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> IntelliSense for variables declared as an interface type (e.g. `Private pBrowser As IExplorerBrowser`) stops providing member completions when the module contains parse-level errors---for example, an unclosed `If...Then` block with a missing `End If`. The IDE cannot resolve interface member completions in the presence of such errors. Resolve all syntax errors in the module first; once the module parses cleanly, interface IntelliSense resumes correctly. + +_Source threads: 1086882000025178112 · confidence: high_ +_Date range: 2023-03-19 to 2023-03-28_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When using `Implements X Via Y` delegation, assigning an object instance to the delegation variable works when `Y` is a local variable. Assigning to `Y` when it is a class-level **Public** field or a field inside a UDT declared at class level may produce a native access violation at runtime. Limit delegation variable assignments to local scope until this restriction is lifted. + +_Source threads: 1305613804180209785 · confidence: low_ +_Date range: 2024-11-11_ +_Reviewer note: Low-confidence finding --- the root cause (VBACopyByte copy becoming invalid after method return) is suspected but not confirmed. Verify against current twinBASIC behavior before publishing. Also confirm whether the 'Implements Via' syntax is documented on this page or elsewhere (the Implements page currently covers only the standard form)._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> Callback interfaces that have no associated GUID and are declared with `Extends Nothing` --- such as those required by the XAudio2 API --- may not be implementable with the **Implements** keyword in twinBASIC. If a callback interface cannot be implemented this way, consider using CoreAudio as an alternative audio API, which is generally supported in twinBASIC. + +_Source threads: 1477515216680911000 · confidence: medium_ +_Date range: 2026-03-02_ +_Reviewer note: This limitation was flagged as a possibility rather than confirmed. Reviewer should test whether twinBASIC actually rejects Implements on a GUID-less Extends-Nothing interface, and whether the issue has been resolved in a subsequent compiler release._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +### Implements WithEvents ... Via + +A class can both implement an interface and forward that interface's events to outside consumers by combining the `WithEvents` and `Via` modifiers: + +```tb +Implements WithEvents InterfaceName Via memberVariable +``` + +The member variable named after `Via` must itself be declared `WithEvents` inside the class. This lets a `WithEvents` variable typed as the interface receive events raised by whichever concrete object is assigned to `memberVariable` at runtime --- making it possible to swap implementations (for example, `StdIO` versus `NamedPipeIO`) while event-handler code written against the interface remains unchanged. + +An example from the twinBASIC runtime source (`ActiveXExtender.twin`): + +```tb +Implements WithEvents T Via ClientObject +``` + +where `T` is the interface type parameter and `ClientObject` is the `WithEvents`-declared member. + +> [!NOTE] +> A known compiler limitation requires at least one `Event` declaration to be present in the class body for the `WithEvents Implements Via` to be included in the compiled output. Without any `Event` on the class, the forwarded events from the interface are silently dropped. A dummy event declaration works around this until the limitation is resolved: +> +> ```tb +> Event xIgnored() ' Required by compiler until limitation is resolved. +> ``` + +_Source threads: 1391490480432349346 · confidence: high_ +_Date range: 2025-07-08_ +_Reviewer note: Verify the exact syntax (Implements WithEvents T Via ClientObject) against ActiveXExtender.twin line 2379 and confirm the FIXME comment about the dummy Event still being present. Place this new subsection inside the existing 'twinBASIC enhancements' section, before the See Also._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> A method does not automatically satisfy an interface contract just because its signature matches the interface method. The implementing method must either follow the traditional underscore naming convention (`IFoo_MethodName`) or append an explicit `Implements IFoo.MethodName` clause after the method signature. Declaring a public method whose name and parameter list match an interface member --- without either convention --- produces a compile error rather than silently binding the two together. + +_Source threads: 1243608396150603838 · confidence: high_ +_Date range: 2024-05-24_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When a class uses `Implements ClassName`, twinBASIC generates an implicit field inside the implementing class with the same name as the implemented class. This field starts as **Nothing** and must be set explicitly if the class intends to delegate to another instance. Accessing this auto-generated field is unusual; the compiler emits a warning --- *"Unusual use of implicit field generated from an Implements statement"* --- when it is referenced. The warning does not prevent compilation. This behavior matches VB6. + +_Source threads: 1397159152203206656 · confidence: high_ +_Date range: 2025-07-22_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When a derived class inherits a base class that already implements an interface, the derived class does not need to re-declare the implementation --- inherited interface members are available automatically unless the derived class overrides them. However, if the derived class needs to implement a second interface that extends the first, there is no syntax to instruct the compiler to use the base class's existing implementation for the shared portion. The only workaround is to add forwarding methods in the derived class that delegate to the inherited implementations. This delegation shorthand is planned for a future release. + +_Source threads: 1484135023694184560 · confidence: high_ +_Date range: 2026-03-19_ +_Reviewer note: Confirm the exact current behaviour against the Inheritance feature page and any related compiler notes -- in particular verify that inherited interface members are truly automatic without re-declaration in the derived class._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> **Resolving interface name collisions with twinpack libraries.** If an interface name imported from a twinpack library collides with a name in another referenced type library (for example, `IEnumVariant` exists in both stdole and a custom twinpack), the compiler may resolve `Implements IEnumVariant` to the wrong type. Qualify the interface name with the package name to disambiguate: +> +> ```tb +> Implements PackageName.IEnumVariant +> ``` +> +> If the qualified form still does not resolve correctly, closing and reopening the twinBASIC IDE (restarting the compiler) has been reported to resolve the issue. + +_Source threads: 1491034044325036173 · confidence: medium_ +_Date range: 2026-04-07 to 2026-04-29_ +_Reviewer note: Verify that package-qualified Implements syntax (PackageName.IEnumVariant) is officially supported. The restarting-the-IDE workaround is anecdotal --- reviewer should confirm whether there is a known compiler bug or whether the restart is coincidental._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> Some COM interfaces declare methods with a true `void` return type rather than `HRESULT`. twinBASIC does not require `[PreserveSig]` to implement these; redeclaring such methods as returning `Long` (HRESULT) works in practice for both 32-bit and 64-bit compiled output --- the same approach used in VB6. Occasional unexpected error values may surface until the interface definition is refined. See [PreserveSig](../../Core/Attributes#preservesig) for the attribute that controls this behavior formally. + +_Source threads: 1208962924820045896 · confidence: medium_ +_Date range: 2024-02-23_ +_Reviewer note: Verify against twinBASIC source whether PreserveSig(True) is the correct formal solution for void-returning COM interface methods, or whether there is another mechanism. The workaround (return Long) was confirmed to work in practice for Direct2D font interfaces._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1478113080419418213] + +### COM implementation gotchas + +Two pitfalls arise specifically when implementing COM interfaces that the Windows OS calls back into: + +**Uninitialized `[out]` parameters.** When the OS calls an interface method and passes a `ByRef ppObject As ISomeInterface` that is semantically write-only (an `[out]` parameter), the incoming value is not guaranteed to be `Nothing` --- it may contain a junk (uninitialized) pointer. A plain `Set ppObject = New SomeClass` will first attempt to release the original value of `ppObject`, which dereferences the junk pointer and crashes. Zero the pointer before the assignment: + +```tb +PutMemPtr VarPtr(ppObject), vbNullPtr +Set ppObject = New SomeClass +``` + +This is different from a twinBASIC-to-twinBASIC call, where the compiler can sanitize callers automatically; when the caller is the OS, no such guarantee exists. + +**`QueryInterface` with the same source and destination variable.** Calling `QueryInterface` and passing the same variable for both the input and output produces a reference leak: + +```tb +' Wrong -- leaks the original reference: +ppObject.QueryInterface(IID_IFoo, ppObject) +``` + +The `QueryInterface` implementation overwrites the output variable without releasing the old reference stored there. Use a separate, initially-Nothing destination variable: + +```tb +Dim pFoo As IFoo +ppObject.QueryInterface(IID_IFoo, pFoo) +Set ppObject = pFoo +``` + +_Source threads: 1386545607916912720 · confidence: high_ +_Date range: 2025-06-25 to 2025-06-26_ +_Reviewer note: These gotchas apply to implementing any Windows COM interface (IShellFolder, IPropertyStore, etc.), not just twinBASIC-defined ones. Verify that PutMemPtr is the canonical pattern used in the community (vs. ZeroMemory or CopyMemory). The IID_IFoo placeholder in the QueryInterface example should be replaced with whatever the docs convention is for GUID arguments._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720] + +> [!NOTE] +> Interfaces declared with `Extends Nothing` (a lightweight interface that does not derive from `IUnknown`) cannot be implemented with **Implements**. Attempting to do so causes a crash at runtime. To satisfy such a callback interface, pass `ObjPtr` of a class instance with a matching vtable layout directly to the API --- the API expects a raw pointer to the vtable, not a COM object reference. This pattern is required for callback interfaces such as `IXAudio2VoiceCallback` that are declared as non-`IUnknown` interfaces. + +_Source threads: 1478113080419418213 · confidence: high_ +_Date range: 2026-03-03_ + +--- + +## docs/Reference/Core/Input.md · after-remarks + +> [!NOTE] +> The name `INPUT` conflicts with the built-in **Input #** statement. A project that tries to declare a `Sub` or `Function` named `INPUT` will encounter this name conflict. As a workaround, choose a different name for the custom routine --- for example, `XINPUT` is a conventional substitute used in BASIC-games ports. + +_Source threads: 1477405818444845328 · confidence: medium_ +_Date range: 2026-02-28_ +_Reviewer note: Verify that this name conflict applies at the compiler level (i.e., twinBASIC rejects a Sub named INPUT), not just at parse time. Confirmed by a user working on a VB6-era BASIC games port._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **`[ComImport(Boolean)]` and COM registration.** When a compiled twinBASIC binary (OCX or DLL) is registered, twinBASIC calls a custom `RegisterTypeLib` implementation that walks each `Interface` declaration and decides whether to write its IID into the registry. The `[ComImport(Boolean)]` attribute controls this decision. It accepts a **Boolean** argument and defaults to `True` for every `Interface` block. +> +> The `True` default is deliberate. On pre-UAC systems (Windows XP and earlier) an unregistered DLL attempts to delete the corresponding registry keys on unregistration. If a project redefines a system interface such as `IShellView`, a `False` default would cause unregistration to delete the live system key, potentially corrupting the OS. With `[ComImport(True)]`, registration is attempted but skipped gracefully when the key is protected; unregistration is a no-op for that entry. +> +> Without this behaviour (present before BETA 243), `RegisterTypeLib` raised a `TYPE_E_REGISTRYACCESS` error when any interface GUID matched a protected system registry key --- for example, when a `UserControl` implemented `IShellFolderViewDual`, `IShellView2`, or `IFolderView2`. VB6 avoided this silently because its `oleaut32` import was shimmed by `aclayers.dll`, which ignored write failures on protected keys and returned `S_OK` regardless. +> +> Set `[ComImport(False)]` explicitly on a brand-new interface that the project defines entirely itself and that should never be written to (or removed from) the system registry. + +> [!NOTE] +> **`[OleAutomation(False)]` does not suppress registration for dual interfaces.** Applying `[OleAutomation(False)]` to an interface that `Extends IDispatch` (a dual interface) does not prevent `RegisterTypeLib` from attempting to register it. The `OleAutomation` flag is honoured only for pure dispatch-only interfaces (`dispinterface`). For dual interfaces the standard `RegisterTypeLib` ignores the flag and proceeds with registration regardless. Use `[ComImport(True)]` (the default) to handle protected-key failures gracefully instead. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-03 to 2023-02-06_ +_Reviewer note: The current page documents `[ComImport]` as a flag only (no boolean argument, line 45). The findings indicate it takes `[ComImport(Boolean)]` with `True` as the default. Verify the exact attribute syntax against the twinBASIC compiler source or BETA 243 release notes before publishing. Also check whether `[ComImport]` (no argument) is still accepted as shorthand for `[ComImport(True)]`._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **`VARIANT_BOOL` mapping and `VARIANT_TRUE`.** The Windows SDK defines `VARIANT_BOOL` as `typedef short VARIANT_BOOL` --- a 2-byte signed integer. Mapping it to `Integer` in an interface member signature is therefore type-size-correct. However, `Boolean` is the semantically preferred type and the one COM infrastructure expects. More importantly, `VARIANT_TRUE` is `-1` (all bits set), not `1`. Interface implementations that return `1` for a true result are incorrect according to the COM specification and may cause misbehavior in COM consumers that check for the canonical `VARIANT_TRUE` value. Always return **True** (which twinBASIC emits as `-1`) rather than a literal `1`. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-06_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **Spurious first member in extended dispatchable interfaces (fixed in BETA 243).** Before BETA 243, when an interface that extended another dispatchable interface was emitted into the generated type library, the compiler inserted a bogus extra first member with a default DispID that conflicted with the real members. The practical symptom was that VB6 could not add the resulting `UserControl` to its component list and reported an automation error. This off-by-one error in the DispID assignment logic was corrected in BETA 243 alongside the custom `RegisterTypeLib` implementation. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-06_ +_Reviewer note: This documents a now-fixed compiler bug. Consider whether the docs site policy is to include version-specific bug history. If the site targets only the current release, this note may be omitted. If it is included, verify BETA 243 is the correct release label._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> The typelib generator does not support forward references between COM interfaces. If interface A declares a method whose return type is interface B, and B is defined later in the source, the build fails with `[TYPELIB] failed to finalize typelibrary interface` and `[TYPELIB] failed to create typelibrary interface procedure ... (bad param datatype)`. The fix is to reorder declarations so that every interface used as a return type or parameter type appears before the interface that references it. This mirrors the requirement for forward declarations in IDL. + +_Source threads: 1071686119554568255 · confidence: high_ +_Date range: 2023-02-05 to 2023-02-07_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> Declaring an interface method with a parameter typed `As Any` (e.g. `Sub Read(pv As Any)`) caused the compiler to crash with `NATIVE EXCEPTION: ACCESS_VIOLATION` on win64 builds when the containing file was subsequently edited. The workaround was to replace `As Any` with `ByVal LongPtr` in the interface declaration. This was fixed in BETA 362. + +_Source threads: 1129802433158189086 · confidence: high_ +_Date range: 2023-07-15 to 2023-07-17_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1392099515309359114] + +> [!NOTE] +> An interface defined with the **Interface** block can be used directly with [**Implements**](Implements) --- there is no separate concept of a "class interface". However, if any member of the interface carries the `[PreserveSig]` attribute, that interface cannot currently be used with **Implements** (support is planned). An interface does not need to occupy its own file; multiple **Interface** blocks can coexist in a single `.twin` file alongside other declarations. + +_Source threads: 1281475744492687465 · confidence: medium_ +_Date range: 2024-09-06_ +_Reviewer note: Verify the [PreserveSig] + Implements limitation against current twinBASIC compiler behaviour; this may have been resolved since September 2024._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465] + +> [!NOTE] +> The Project Explorer does not have a dedicated "Add Interface" option. To define a public interface, add a Class or Module `.twin` file to the project and write the `Interface ... End Interface` declaration at file scope, before any `Class...End Class` or `Module...End Module` block. When adding an `[InterfaceId(...)]` attribute, the IDE prompts to generate a GUID using a special algorithm that ensures global uniqueness --- use that rather than supplying a self-generated GUID. + +_Source threads: 1392099515309359114 · confidence: high_ +_Date range: 2025-07-08_ + +--- + +## docs/Reference/Core/LSet.md · after-remarks [DUPLICATE? -- see also thread 1111237398291021934] + +> [!NOTE] +> The raw-memory-copy behavior of **LSet** between differently-typed UDTs applies only to *simple* UDTs --- those whose fields are all fixed-size numeric types. When a UDT contains `String`, `Object`, `Variant`, or dynamic-array fields, **LSet** copies each field individually using the same semantics as a normal assignment, not a raw byte copy. As a result, assigning an `Object` field from one UDT into a `Long` field of another via **LSet** does not extract the raw pointer value; the runtime applies field-level type rules instead. + +_Source threads: 1053405466409058335 · confidence: high_ +_Date range: 2022-12-17_ + +--- + +## docs/Reference/Core/LSet.md · after-remarks [DUPLICATE? -- see also thread 1053405466409058335] + +> [!NOTE] +> The UDT-copy form of **LSet** requires both *varname1* and *varname2* to be variables of user-defined types. Assigning a scalar value---such as a `LongPtr` or any numeric type---directly into a UDT variable using **LSet** is not valid and produces a compilation (codegen) error. To overlay raw bytes from a scalar into a UDT, use `CopyMemory` or the `PutMem` / `GetMem` helpers in the VBA `(Default)` module. + +_Source threads: 1111237398291021934 · confidence: medium_ +_Date range: 2023-05-25_ +_Reviewer note: Verify the exact error message text and confirm that CopyMemory / PutMem is the intended workaround for this pattern in twinBASIC. The Discord thread confirmed this is a language rule (not a twinBASIC-specific bug), but the recommended alternative should be checked against current twinBASIC practice._ + +--- + +## docs/Reference/Core/LeftShift.md · after-remarks + +> [!NOTE] +> VB6 has no explicit bit-shift operators, so code written for VB6 sometimes multiplies or divides by powers of two (`i * 2`, `i * &H4`) to perform a logical shift, relying on the compiler's optimizer to emit a shift instruction. In twinBASIC, using **\<\<** and **\>\>** directly is preferred---it is clearer in intent and does not depend on optimizer behaviour. + +_Source threads: 1112496566176075897 · confidence: medium_ +_Date range: 2023-05-28 to 2023-05-29_ +_Reviewer note: The same note could appear on the RightShift page (docs/Reference/Core/RightShift.md) with minor wording adjustment; add a cross-reference there if this addition is accepted._ + +--- + +## docs/Reference/Core/Module.md · after-remarks + +> [!NOTE] +> `[MustBeQualified]` has no effect when placed on a **Module** statement that is wrapped inside a `#If` ... `#End If` conditional compilation block, even when the condition evaluates to **True** and other attributes in the same block work correctly. Module members remain accessible in the global namespace as if the attribute were absent. Using `[MustBeQualified(True)]` does not resolve the issue. The only known workaround is to place the attribute unconditionally outside any `#If` guard. + +_Source threads: 1431883663796207707 · confidence: medium_ +_Date range: 2025-10-26 to 2026-02-05_ +_Reviewer note: Verify against the current twinBASIC build whether this bug has been fixed. The thread spans late 2025 to early 2026._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> twinBASIC supports parameterized constructors. A class can define `Public Sub New(Param1 As Type, ...)`, and instances are then created with `Set obj = New ClassName(arg1, arg2)`. VBA has no equivalent --- VBA code must use a factory method pattern instead. +> +> A class that is COM-creatable (the default) must also expose a parameterless `Public Sub New()` alongside any parameterized constructor, because COM requires that COM-creatable classes be instantiable without parameters. Defining only a parameterized constructor on a COM-creatable class causes a compilation error. To remove this constraint, mark the class `[COMCreatable(False)]`; the class can then expose only the parameterized constructor, but it cannot be created by COM clients outside twinBASIC. + +_Source threads: 1086699374073085992 · confidence: medium_ +_Date range: 2023-03-18 to 2023-03-19_ +_Reviewer note: Verify against the twinBASIC compiler source or a current .twin sample that (a) `Set obj = New ClassName(arg1)` is the correct instantiation syntax for parameterized constructors, and (b) the compilation error is actually triggered when a COM-creatable class has only a parameterized Sub New. Confidence reduced to medium because the gotcha detail came from a single community message._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> When a COM type library marks a class with `TYPEFLAG_FCONTROL` (the ActiveX control flag), twinBASIC treats it as an ActiveX control. Declaring such a class by its coclass name (e.g. `Dim sc As MSScriptControl.ScriptControl`) and instantiating it with **New** causes the compiler to substitute the generated ActiveX wrapper interface for the declared type. Because the object is created directly with **New** rather than placed on a form, it is not wrapped in twinBASIC's ActiveX extender and does not support that wrapper interface --- producing a runtime `E_NOINTERFACE` / "No such interface supported" error at assignment. `MSScriptControl.ScriptControl` exhibits this behavior; the same code works in VB6 only when added via the References dialog, not the Components (ActiveX controls) dialog. +> +> Two fixes are available: +> +> - **Project Settings fix (permanent):** in Project Settings, turn off the "Use ActiveX Controls" flag for the reference, save, and restart the compiler. twinBASIC then treats the class as a plain COM reference and the coclass declaration works normally. +> - **Declaration workaround:** declare the variable as the class's default interface rather than the coclass name (`Dim sc As IScriptControl`). The **New** expression on the right-hand side still creates the object correctly; only the variable's declared type needs to be the interface: +> +> ```tb +> Dim sc As IScriptControl +> Set sc = New MSScriptControl.ScriptControl +> ``` + +_Source threads: 1110498079972540436 · confidence: high_ +_Date range: 2023-05-23_ +_Reviewer note: Verify that the 'Use ActiveX Controls' flag name and location in Project Settings matches the current IDE UI. The workaround using IScriptControl as the declared type was confirmed by multiple Discord users in May 2023._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!WARNING] +> Specifying an interface type (rather than a concrete CoClass name) with **New** causes an ACCESS_VIOLATION crash at startup. For example, `Dim x As New IFoo` --- where `IFoo` is an **Interface**, not a **CoClass** or **Class** --- produces a native exception rather than a compile-time or graceful run-time error. The crash occurs even when the declaration is inside a procedure that is never called during startup. Always use the concrete CoClass or Class name with **New**. + +_Source threads: 1291608868673294336 · confidence: high_ +_Date range: 2024-10-04 to 2024-10-06_ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> When a class has both a `New` constructor (a twinBASIC addition) and a `Class_Initialize` or `Form_Initialize` event handler, the `New` constructor executes first, then `Initialize` fires. Both still fire --- neither suppresses the other. This ordering matters when a class has its predeclared ID set to `True`, because the default instance is created at application startup: code that relies on field values set in `Initialize` may run before the constructor has had a chance to supply them. + +_Source threads: 1145039659911622656 · confidence: medium_ +_Date range: 2023-08-26 to 2023-08-30_ +_Reviewer note: Verify constructor-vs-Initialize ordering against .twin sources or twinBASIC release notes; the medium-confidence source is a Discord discussion about VB6 MVVM porting patterns._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> twinBASIC supports overloaded `Sub New(...)` constructors, but two limitations apply: +> +> **Parameter types:** Overload resolution fails when any overloaded `New` signature uses `Object` or `Variant` as a parameter type. These types prevent the overload discriminator from distinguishing candidates. Wrapping the ambiguous value in a `Type` (UDT) is the recommended workaround. +> +> **No constructor chaining:** There is no syntax to call one overloaded constructor from another (as C# allows via `this(...)`). Attempting it produces compile errors. Extract shared initialisation logic into a private method and call it from each overloaded constructor instead. + +_Source threads: 1357331478786080972 · confidence: medium_ +_Date range: 2025-04-03_ +_Reviewer note: Verify against twinBASIC compiler source or release notes: confirm the Object/Variant overload resolution limitation and that constructor chaining is explicitly unsupported._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1468031072448020706] + +> [!NOTE] +> The syntax `New ClassName(args).Method` --- calling a method directly on a freshly constructed object --- is not yet supported. `With New ClassName(args)` compiles and works correctly at runtime. Wrapping in parentheses, `(New ClassName(args)).Method`, parses but fails at runtime with error 438 (Object doesn't support this property or method). A common workaround is to define a module-level factory function with the same name as the class: +> +> ```tb +> Function MyClass(ByVal Name As String) As MyClass +> Set MyClass = New MyClass(Name) +> End Function +> ``` +> +> This enables the `MyClass("foo").Method` fluent pattern without assigning the object to a variable first. + +_Source threads: 1357675256252469454 · confidence: high_ +_Date range: 2025-04-04 to 2026-01-28_ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454] + +## Parameterized constructors + +A class can declare a **Sub** named **New** with one or more parameters to act as a parameterized constructor. The twinBASIC runtime invokes this **Sub** automatically when the class is created with **New**, passing the arguments from the **New** expression: + +```tb +' Declaration (in the class body): +Private Sub New(ByVal title As String, ByVal width As Long) + Caption = title + Me.Width = width +End Sub + +' Creation (at the call site): +Dim w As New MyWindow("Settings", 4000) +' or: +Set w = New MyWindow("Settings", 4000) +``` + +> [!NOTE] +> The access modifier on `Sub New` can be **Private** (or any other modifier). Declaring it **Private** prevents external code from calling it as a regular method while still allowing the **New** expression to invoke it normally. The `Sub New` declaration can appear anywhere in the class body. + +> [!IMPORTANT] +> When a class defines a parameterized `Sub New`, the **Class_Initialize** event is never raised for that class --- the two mechanisms are mutually exclusive. Removing the `Sub New` declaration restores **Class_Initialize** behaviour. If initialisation logic must be shared, call the relevant code from inside `Sub New` explicitly; **Class_Initialize** will not fire automatically. + +_Source threads: 1468031072448020706 · confidence: high_ +_Date range: 2026-02-02 to 2026-02-03_ +_Reviewer note: New.md currently documents only the `New` expression keyword. This draft adds a new subsection about `Sub New` parameterized constructors. Verify against the twinBASIC .twin sources / Inheritance feature docs that the Class_Initialize suppression is correct and intentional._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1113589601232240741, 1351545697060524033, 1478843597578174514] + +> [!NOTE] +> When a runtime error occurs and no error handler is active, the procedure exits at that point. The exit is silent---no dialog appears and no further statements in the procedure run. Output produced before the error is retained, but any output that would have followed is silently omitted. This can make a logic error appear as unexpected early termination or missing results rather than an obvious error. Adding an `On Error GoTo` handler that logs `Err.Number` and `Err.Description` is the most reliable way to diagnose such failures. + +_Source threads: 1201183841113612419 · confidence: high_ +_Date range: 2024-01-28_ +_Reviewer note: The original thread (BETA 424+) described this as a bug fixed in BETA 432. Verify whether the current runtime still exits silently on unhandled errors or now shows a dialog, and adjust the note accordingly._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1201183841113612419, 1351545697060524033, 1478843597578174514] + +> [!NOTE] +> Structured exception handling (`Try`/`Catch`/`Finally`) is not available in the current release. Support is planned via vbWatchDog integration; see [lang-design issue #61](https://github.com/twinbasic/lang-design/issues/61) for status. Until that ships, `On Error GoTo` combined with a cleanup label is the only built-in mechanism. + +_Source threads: 1113589601232240741 · confidence: medium_ +_Date range: 2023-05-31 to 2023-06-01_ +_Reviewer note: Verify whether the vbWatchDog Try/Catch integration has shipped or the issue has been closed since mid-2023._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1201183841113612419, 1113589601232240741, 1478843597578174514] + +> [!NOTE] +> When execution has entered an error-handling block --- that is, after an `On Error GoTo