Online edition for BLOGE
0.9.8-RC1· facts verified 2026-09-15 · 中文
Chapter 9 — Tooling Workflow
Promise: By the end of this chapter you will understand how the BLOGE tooling stack — from its TypeScript authoring path and Java execution path to the editor, the visual studio, and the CLI linter — works together, and you will know how to set up a productive authoring loop for
.blogefiles that catches errors before code review and production.
Learning Goals
- Map the two implementation paths: TypeScript
bloge-langfor LSP/Studio authoring, and Javabloge-dslplusbloge-lintfor runtime/CI. - See how diagnostics flow from the lexer, parser, compiler, and lint rules through the LSP to red squiggles in your editor.
- Use format-on-save to keep
.blogefiles consistent via the DSL code generator. - Run the
bloge-lintCLI in CI and understand why its Java rule set overlaps with, but is not identical to, editor diagnostics. - Trace
operator-metadata.jsonfrombloge-maven-plugininto BLOGE Studio's operator palette, then use split view to round-trip between DSL text and the canvas.
Prerequisites
- Chapter 8 — Turning a Personal DSL Draft into a Team Asset — you need to be comfortable with a complete
.blogefile. - Chapter 2 — Your First Graph — you should know how to read compile and execution results.
- Basic familiarity with VS Code or IntelliJ IDEA.
Source Examples
| File | What it shows |
|---|---|
bloge-lang/README.md | TypeScript authoring core: lexer, parser, compiler, codegen, schema |
bloge-lsp/README.md | LSP server capabilities and architecture |
bloge-lsp/src/server.ts | LSP server entry — capability registration |
bloge-lsp/src/document-manager.ts | Five-stage analysis pipeline (lex → parse → compile → lint → index) |
bloge-lsp/src/features/hover.ts | Hover docs for keywords, built-in functions, nodes, schemas, and durations |
bloge-lsp/src/features/completion.ts | Context-aware auto-completion |
bloge-lsp/src/features/formatting.ts | Whole-document formatting via DslCodeGenerator |
bloge-lsp/src/lint/lint-rule.ts | LintRule interface and LintRunner |
bloge-vscode/src/extension.ts | VS Code extension entry — LSP client wiring |
bloge-vscode/syntaxes/bloge.tmLanguage.json | TextMate grammar for syntax highlighting |
bloge-intellij/README.md | IntelliJ plugin — dual-mode architecture (PSI + optional LSP overlay) |
bloge-studio/README.md | Visual DAG editor — React 19 + React Flow |
bloge-studio/public/operator-metadata.json | Built-in operator catalog (name, description, I/O schema) |
bloge-maven-plugin/README.md | Maven plugin that generates operator-metadata.json from @BlogeOperator classes |
bloge-lint/README.md | Java CLI linter — rules, .blogerc.json config, exit codes |
ticket-routing.bloge | Example used throughout this chapter |
missing-timeout.bloge | Antipattern caught by lint |
Why This Matters
You can write syntactically correct .bloge files in a plain text editor.
But without tooling:
- Typos in node IDs are discovered only at graph-engine start time.
- Unused nodes and missing timeouts slip past review unnoticed.
- Formatting drift makes PRs noisy and obscures real changes.
- Operator schema mismatches between the Java code and the DSL are invisible until a runtime
ClassCastException.
The BLOGE tooling chain is designed so that the fastest feedback loop is also the cheapest: red squiggles appear as you type, formatting normalises on save, and the CLI linter runs in CI as a final gate. These surfaces do not share one implementation: TypeScript powers the LSP and Studio, while Java powers runtime parsing and CLI lint. Conformance fixtures and exported contracts keep the paths aligned.
Mental Model
Think of the tooling stack as two feedback paths joined by explicit compatibility evidence:
The critical insight: shared contracts are the alignment boundary, not shared code. bloge-lang supplies the TypeScript parser/compiler used by bloge-lsp and Studio. Runtime and Maven flows use Java bloge-dsl; bloge-lint adds Java CI rules; IntelliJ Community keeps a PSI/JFlex path and can add an LSP overlay where supported. Because implementations can drift, compatibility must be checked rather than assumed.
First Working Example
Open the
ticket-routing.bloge
example in VS Code with the bloge-vscode extension installed. You should
see immediately:
-
Syntax highlighting — keywords like
graph,node,branch,depends_onare coloured; doc comments (///) get documentation-comment scopes; operator references likeFetchCustomerOperatorare highlighted as types. -
Diagnostics — if you deliberately misspell a
depends_ontarget (say, changefetchCustomertofetchCustmer), a red underline appears before you save. -
Hover — hover over
timeoutand you see: "Sets the maximum execution time for a node." Hover over3sand you see: "Duration: 3s = 3000ms". Hover overanalyzeSentimentand you see its operator ref, doc comment, dependencies, and timeout. -
Completion — inside an
input { }block, type=and the LSP suggestsctx, all node IDs, and built-in functions likeconcat,size, andcoalesce. -
Formatting — trigger "Format Document" and the entire file is re-emitted through
DslCodeGenerator.generate(ast), normalising indentation and spacing.
All five features are powered by a single pipeline defined in
document-manager.ts:
// Phase 1: Lexing → tokens
// Phase 2: Parsing → AST (GraphDef)
// Phase 3: Compile → CompiledGraph + diagnostics
// Phase 4: Lint → LintRunner.run(ast, compiled)
// Phase 5: Index → buildSymbolIndex(ast, uri, text)
This five-stage pipeline runs on every keystroke (via
documents.onDidChangeContent in
server.ts), and the cached ParseState
is reused by hover, completion, definition, references, and rename handlers.
After those five phases, the DocumentManager also refreshes import-graph
bookkeeping so cross-file features stay in sync.
Follow One Pull Request Through the Feedback Loop
A toolchain earns its place when it shortens the distance from an edit to a
reviewable fact. Consider a pull request that removes timeout from an external
call in missing-timeout.bloge. The useful story is not a tour of editor
features; it is one defect travelling through four feedback surfaces.
One defect, one stable identity
| Surface | Reader sees | Required hand-off |
|---|---|---|
| Editor | Inline diagnostic at the node | ruleId=missing-timeout and source location |
| CLI lint | Reproducible non-zero result | Same rule id and severity as CI |
| Scenario test | Runtime behaviour for a representative case | Case verdict plus node/effect evidence |
| Pull-request review | Why the edit is safe | Diagnostic cleared and focused test result |
Autocomplete can help write syntax, but it cannot close this loop. The shared contract is the stable diagnostic identity plus a command that another person can rerun.
Diagnose before testing everything
The shortest useful sequence is:
EDIT one declaration
→ DIAGNOSE at the source location
→ LINT with the same rule set as CI
→ TEST the smallest Scenario that exercises the changed path
→ ATTACH both results to review
If lint still fails, return to the declaration; a full integration suite adds noise. If lint passes but the Scenario fails, inspect runtime evidence; changing the lint configuration cannot repair business behaviour. Only after both focused signals pass should the broader build run.
This keeps claims bounded: lint proves a declared authoring rule, the Scenario proves behaviour under its fixtures, and neither alone proves production correctness.
Break It Apart
1 — The shared language core: bloge-lang
bloge-lang is a pure TypeScript library
with zero dependencies. It exports:
Lexer— hand-written scanner producing tokens from.blogesource.Parser— recursive descent with precedence-climbing expression parsing. Handles imports, graph-levelinput/outputschemas,signal_schema,oneOf(...), union types,?.safe navigation, and more.compile(ast, options?)— frontend semantic analysis: dependency resolution, cycle detection, schema validation, union narrowing, branch exhaustiveness checking. When aCompileOptions.importResolveris provided, imports are resolved recursively.DslCodeGenerator.generate(ast)— AST-to-source formatter. This is the same code path used by both the LSP formatting feature and Studio's export.
Usage in three lines:
import { Lexer, Parser, compile, DslCodeGenerator } from 'bloge-lang';
const tokens = new Lexer(source).tokenize();
const ast = new Parser(tokens).parse();
const result = compile(ast);
// result.diagnostics → errors, warnings
// DslCodeGenerator.generate(ast) → formatted source
2 — The LSP: bloge-lsp
bloge-lsp wraps bloge-lang in a Language
Server Protocol server that communicates over stdio. The
server.ts entry point registers these
capabilities:
| Capability | Handler file | What it does |
|---|---|---|
| Diagnostics | document-manager.ts | Real-time error/warning/info from lexer + parser + compiler + lint |
| Completion | features/completion.ts | Context-aware: keywords, node IDs, schema types, built-in functions, oneOf(...) and snippet templates |
| Hover | features/hover.ts | Docs for keywords, ~70 built-in functions, node/schema/transform declarations, durations |
| Go to Definition | features/definition.ts | Symbol-index-backed jump to declaration's nameRange; cross-file via resolveSymbolCrossFile |
| Find References | features/references.ts | All usages of a node/schema/transform, including transitive importers |
| Rename | features/rename.ts | Renames declaration + all usages; propagates to transitive importers; conflict-checked |
| Formatting | features/formatting.ts | Whole-document via DslCodeGenerator.generate(ast) |
| Code Actions | features/code-actions.ts | Quick fixes for missing-timeout, missing-doc-comment, unused-node, no-duplicate-node-id; plus a Source Fix All action for safe bulk fixes |
| Semantic Tokens | features/semantic-tokens.ts | Full-document semantic highlighting: graph names, operator refs, node refs, path segments, literals, keywords — refines TextMate scopes with AST-aware token classes |
Import resolution is automatic for file:// documents — the
DocumentManager wires a file-backed ImportResolver that resolves paths
relative to the open file and appends .bloge when needed. Errors in
imported files surface as diagnostics in the importing document.
3 — Lint rules
BLOGE lint is tri-layered. Knowing which layer a rule lives in tells you where it runs, what context it can see, and which artifact you must rebuild to add a new rule.
| Layer | Where it runs | What it sees | Examples |
|---|---|---|---|
| Core (15) | bloge-lint CLI + LSP (translated to TS) | A single .bloge file's AST after parse + compile | no-duplicate-node-id, no-duplicate-schema-name, unused-node, missing-timeout, excessive-fan-out, missing-doc-comment, no-cycle, no-unresolved-dependency, no-unresolved-branch-target, max-script-nodes, script-timeout-required, script-line-limit, loop-exit-completeness, cron-expression-valid, deadline-in-past |
| Compiler-driven (3) | Inside the compiler itself | Whole-graph IR after symbol resolution and type checking | schema-validation-strictness-mismatch, dynamic-subgraph-output-untyped, ambiguous-timer |
| Extension (2 today, more via SPI) | Discovered when the matching *-ext JAR is on the classpath | The extension's own DSL surface | session-backward-transition-guard (from bloge-session-ext), state-machine-structure (from bloge-state-ext) |
Core rules — LSP subset
The LSP bundles the six interactive rules
(bloge-lsp/src/lint/rules/):
| Rule ID | Severity | What it catches |
|---|---|---|
no-duplicate-node-id | Error | Node ID declared more than once |
no-duplicate-schema-name | Error | Schema name declared more than once |
unused-node | Info | Node never referenced downstream |
missing-timeout | Info | Node without a timeout |
excessive-fan-out | Info | Node with >5 outgoing edges |
missing-doc-comment | Info | Node missing /// documentation |
The Java-side CLI linter (bloge-lint)
includes these same core rules for batch checks and adds the graph-level and
script-focused rules listed above, plus the rules contributed by extensions
via the LintExtensionContributor SPI.
Compiler-driven rules
These three rules are emitted by the compiler, not the rule runner.
You cannot disable them in .blogerc.json — they reflect structural
checks the compiler must run to produce a valid Graph. They surface as
ordinary diagnostics in the LSP and CLI just like core rules.
Extension rules
Adding bloge-session-ext or bloge-state-ext to your project also adds
that extension's lint contributions. You can register your own rules via
LintRuleProvider (Java SPI) for the CLI and BLOGE_LSP_EXTRA_RULES for
the LSP. Severity levels and rule parameters are configurable through
.blogerc.json:
{
"rules": {
"missing-timeout": "warning",
"missing-doc-comment": "off"
}
}
Decision-table rules
The bloge LSP and CLI both ship two lint rules specifically for decision_table
nodes. Use these recipes to keep tables exhaustive and intentional.
| Rule ID | Severity | Triggers when |
|---|---|---|
decision-table/missing-otherwise | WARNING | A first, unique, or any table has no otherwise clause |
decision-table/collect-otherwise | INFO | A collect table has an otherwise clause (always appends — may be unintentional) |
Recipe 1 — Silence decision-table/missing-otherwise by adding otherwise:
// [WARN] decision-table/missing-otherwise
decision_table credit_tier(score = ctx.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
// no otherwise — what happens if score < 680?
}
// ✅ Fixed
decision_table credit_tier(score = ctx.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
Recipe 2 — Acknowledge decision-table/collect-otherwise if it is intentional:
// [INFO] decision-table/collect-otherwise
// "baseline" is ALWAYS appended, even when other rules also matched.
decision_table discounts(score = ctx.score) hit=collect -> String {
rule (score: score >= 700) -> "loyalty"
otherwise -> "baseline" // unconditional — is this intentional?
}
// ✅ If intentional, suppress with a .blogerc.json override:
// { "rules": { "decision-table/collect-otherwise": "off" } }
// If not intentional, remove the otherwise clause.
For a full list of compiler and runtime error codes, see Appendix F — Decision Table.
4 — Editor extensions
VS Code (bloge-vscode):
- TextMate grammar at
syntaxes/bloge.tmLanguage.jsonprovides syntax highlighting scopes for keywords, doc comments, strings, operators, types, and built-in functions. - Language configuration at
language-configuration.jsonprovides bracket matching, auto-closing pairs, comment toggling (//,/* */), and block folding. - The extension entry (
extension.ts) launchesbloge-lspas a child process over stdio and wires it as the LSP client for theblogelanguage ID.
IntelliJ IDEA (bloge-intellij):
- Community edition — PSI-only mode: hand-written JFlex lexer, lightweight
PsiBuilderparser,ExternalAnnotatordelegates tobloge-dslParser andbloge-lintLintRunner, structure view, code folding, semantic highlighting. No external process required. - Ultimate / commercial editions — optional LSP overlay:
BlogeLspServerSupportProviderlaunches the bundledbloge-lspNode server; overlapping PSI providers (diagnostics, completion, formatting) stand down to avoid duplicate results. Requires Node.js onPATHorBLOGE_NODE_PATH.
5 — BLOGE Studio
BLOGE Studio is a browser-based visual
DAG editor built with React 19 + React Flow 12. It depends on bloge-lang
as a local file: dependency and reuses the same parser, compiler, and code
generator.
Key features relevant to your authoring workflow:
- Three view modes: Visual (canvas only), Code (Monaco editor only), Split (canvas + editor side by side). The Zustand store synchronises between them.
- Operator Palette: loaded from
public/operator-metadata.json. Operators are tagged by layer (infra / capability / domain) and searchable. Drag to canvas to create a node. - Diagnostics Panel:
DiagnosticsPanel.tsx— displays errors, warnings, and info from the compiler and lint rules. Click a diagnostic to select the offending node on the canvas. - Undo/Redo: DSL-snapshot-based history (up to 50 steps).
- Export: validates → runs code generator → downloads
.blogefile.
6 — The operator metadata pipeline
The link between your Java operator code and the visual tooling is
bloge-maven-plugin:
The plugin scans @BlogeOperator-annotated classes at process-classes
phase, reflects Operator<I,O> generic parameters, calls
SchemaIntrospector.introspect() for each type (with Record and POJO
support), and writes a JSON file. Studio loads this file to populate the
operator palette and provide I/O schema information for field completion.
The Embedded Ops Console — /bloge-console
Studio is great for authoring graphs. Production needs a different view:
"which executions are running right now, which timer fires next, which
operator is throwing." That view is /bloge-console — a small,
read-only operator UI that ships with bloge-spring-web and mounts at
the configured base path.
spring:
bloge:
console:
enabled: true
base-path: /bloge-console # default
require-role: BLOGE_OPS # Spring Security role gate
What it shows:
- Executions — running, waiting, completed, failed; click into the audit journal for a single run.
- Timers — currently armed durable timers with their next fire time;
manual fire now (gated by
require-role). - Leases — current owners with TTL remaining.
- Schema — Flyway
currentVersion, pending/applied migrations, andUP_TO_DATE,PENDING, orFAILEDstatus. - Operator registry — every registered operator, its module, and its
@BlogeOperatormetadata for quick discovery.
The console is observational by design. It does not let you edit graphs (use Studio) or rewrite checkpoints (use a migration). The "fire timer" and "release lease" actions are explicit operator-recovery tools and every action is recorded in the audit journal under the calling principal.
Brain check: when should you reach for
/bloge-consolevs Studio? Hint: Studio writes the graph; the console reads the runtime.
Common Trap
❌ Assuming the editor and CI check the same rules
The LSP lint rules (TypeScript, in bloge-lsp) and the CLI lint rules (Java,
in bloge-lint) are two separate implementations with overlapping but
not identical rule sets. The LSP has unused-node and excessive-fan-out;
the CLI has no-unresolved-dependency, no-unresolved-branch-target, and
no-cycle. If you rely only on green squiggles in your editor, CI may still
fail on rules the LSP does not check.
# Run the Java linter locally before pushing:
java -jar bloge-lint.jar check src/main/resources/bloge/
# Or configure it in your build:
# .blogerc.json controls severity overrides for both editor and CLI usage.
Best practice: run bloge-lint check as a pre-commit hook or CI step so
that the full rule set is applied before merge.
Guided Rewrite
Take the missing-timeout.bloge
antipattern file and use the tooling to fix it:
-
Open it in VS Code with the
bloge-vscodeextension. You should see an info diagnostic onloadReportfrom themissing-timeoutlint rule. -
Hover over
loadReportto confirm there is no timeout listed in the hover popup. -
Add a timeout:
node loadReport : LoadReportOperator {input {reportId = ctx.reportId}timeout = 5s} -
Format the document (Shift+Alt+F in VS Code). The
DslCodeGeneratornormalises the indentation. -
Add a
///doc comment aboveloadReport. Confirm themissing-doc-commentdiagnostic disappears. -
Run the CLI linter against the file:
java -jar bloge-lint.jar check src/main/resources/bloge/antipatterns/missing-timeout.blogeWith the timeout and doc comment added, the output should report zero errors.
-
Open the same file in BLOGE Studio (
npm run devinbloge-studio/, then use "Open" in the toolbar). Switch to Split view and observe the canvas and DSL code in sync. Check the Diagnostics panel at the bottom — it should be empty.
Brain Check
-
Which module does the LSP's formatting feature actually delegate to? (The
DslCodeGeneratorfrombloge-lang. Seeformatting.ts— it callsDslCodeGenerator.generate(state.ast).) -
How does the LSP detect that a
depends_ontarget is misspelled? (compile(ast)inbloge-langruns dependency resolution. An unresolvable dependency produces anerror-level diagnostic, which theDocumentManagerconverts to an LSPDiagnosticSeverity.Error.) -
What happens in the IntelliJ plugin when the IDE is Community edition and Node.js is not installed? (The plugin operates in PSI-only mode — all features are provided by the built-in JFlex lexer, PsiBuilder parser, and ExternalAnnotator. The LSP overlay never activates because
com.intellij.modules.lspis absent.) -
How does BLOGE Studio know which operators to show in its palette? (It loads
public/operator-metadata.jsonon startup. This file can be generated bybloge-maven-plugin'sexport-metadatagoal, or hand-authored.) -
Can you disable a lint rule in the CLI? How? (Yes — create a
.blogerc.jsonfile with arulesmap. Set the rule ID to"off". For example:{ "rules": { "missing-doc-comment": "off" } }. SeeLintConfig.java.)
Lab
-
Editor setup: Install the
bloge-vscodeextension (or run the IntelliJ plugin sandbox with./gradlew runIdeinbloge-intellij/). Openticket-routing.bloge. Verify that:- Syntax highlighting covers all keywords and doc comments.
- Hovering over
analyzeSentimentshows its operator, description, dependencies, and timeout. - Ctrl+Click on
fetchCustomerin thedepends_onlist jumps to the node declaration. - Renaming
classifyPriorityupdates the branch condition and alldepends_onreferences.
-
Break a graph on purpose: In
ticket-routing.bloge, changedepends_on = [analyzeSentiment]onclassifyPrioritytodepends_on = [nonExistentNode]. Observe the diagnostics that appear. Then runbloge-lint checkon the file and compare the CLI output to the editor squiggles. -
Operator metadata round-trip: If you have a Maven project with
@BlogeOperatorclasses, runmvn bloge:export-metadataand copy the resultingoperator-metadata.jsonintobloge-studio/public/. Start Studio and confirm your operators appear in the palette with correct I/O schemas. -
Studio split-view: Open
ticket-routing.blogein BLOGE Studio. Switch to Split view. Add a new node in the code editor and watch it appear on the canvas. Delete it and use Undo (Ctrl+Z) to bring it back.
Bridge: from tooling to a verification gate
Editor diagnostics and bloge-lint shorten authoring feedback; they do not execute an approved business Scenario. In a normal customer module, enter the 0.9.8-RC1 verification path by compiling the bootstrap and running the Preview goal against .blogeverify.yaml in one Maven invocation:
mvn test-compile \
com.leanowtech.bloge:bloge-maven-plugin:0.9.8-RC1:verify
The generated artifact still needs source binding and a receipt retained outside its mutable directory. In RC1, this book's loan starter fails closed because its com.leanowtech.bloge.starter.* bootstrap hits the plugin classloader boundary; run its JUnit Scenario tests instead, and do not interpret TEST_CLASSPATH_UNAVAILABLE as a business failure. Chapter 32 — Change Attribution, Maven, and Release Decisions explains source installation, the known limitation, the command ladder, and the claim boundary.
Experiment acceptance card
- Expected and observed: A missing-timeout defect crosses edit, diagnose, lint, Scenario, and review as one loop.
- Failure and recovery: Remove timeout to fail the gate; restore it and rerun the smallest check set.
- Proof boundary: Proves the toolchain blocks a declared defect, not an unencoded rule.
- Exercise contract: One PR; change one defect; deliver diagnostic, test, and review receipts; stop when all point to one rule.
Recap
bloge-langis the TypeScript authoring core used by the LSP and Studio; it is not the Java runtime parser.bloge-dslis the Java runtime/Maven parsing path; conformance fixtures and exported contracts are the compatibility boundary between the two implementations.bloge-lspwrapsbloge-langin an LSP server that provides diagnostics, completion, hover, go-to-definition, find references, rename, and formatting over stdio.bloge-vscodeprovides TextMate syntax highlighting and language configuration, then delegates all intelligent features tobloge-lsp.bloge-intellijworks in PSI-only mode on Community edition and adds an optional LSP overlay on Ultimate/commercial editions.- BLOGE Studio is a browser-based visual editor with a three-way view
(visual / code / split), a diagnostics panel, and an operator palette
driven by
operator-metadata.json. bloge-maven-pluginbridges Java operator code to the visual tooling by generatingoperator-metadata.jsonfrom@BlogeOperatorannotations.bloge-lint(Java CLI) runs the same style of lint rules in CI; its rule severity is configurable via.blogerc.json.- The LSP and CLI lint rule sets overlap but are not identical — always run the CLI linter in CI as a final gate.
Next Step
In Chapter 10 — Reuse with Subgraphs, you will use this feedback loop while extracting reusable graph behavior behind an explicit parent/child contract.
Reference Links
bloge-lang/README.md— TypeScript authoring-core documentationbloge-lsp/README.md— LSP server documentation and architecture diagrambloge-lsp/src/document-manager.ts— five-stage analysis pipeline sourcebloge-lsp/src/lint/lint-rule.ts—LintRuleinterface and built-in rule setbloge-vscode/README.md— VS Code extension documentationbloge-vscode/src/extension.ts— LSP client wiringbloge-vscode/syntaxes/bloge.tmLanguage.json— TextMate grammarbloge-intellij/README.md— IntelliJ plugin documentation and dual-mode architecturebloge-studio/README.md— Visual DAG editor documentationbloge-studio/src/components/panels/DiagnosticsPanel.tsx— Studio diagnostics panel sourcebloge-studio/public/operator-metadata.json— built-in operator catalogbloge-maven-plugin/README.md— Maven plugin for operator metadata generationbloge-lint/README.md— Java CLI linter documentationbloge-lint/src/main/java/.../LintConfig.java—.blogerc.jsonconfiguration parserbloge-lint/src/main/java/.../BlogeLintCli.java— CLI entry pointticket-routing.bloge— primary examplemissing-timeout.bloge— antipattern fixture
Coding Agent: Open the versioned task guide.