User Guide
Using the AlignThree IDE: the workspace, writing specifications, tables, tags, generating code, Analyze, sharing work, and the keyboard reference.
(formerly named SpecStudio)
AlignThree is an IDE for writing specifications as tables and turning them into
runnable tests. You write a .spectable file describing what the software
should do; AlignThree generates the test code and the glue that connects those
tests to your production classes, in any of nine languages.
The specification is the source of truth. The generated tests are disposable — regenerate them whenever the specification changes.
Contents
- Getting started
- The workspace
- Writing a specification
- Working with tables
- Seeing what you wrote
- Navigating and renaming
- Configuration
- Generating code
- What gets generated
- Testing an API
- Analyze
- Sharing work
- Settings
- Keyboard reference
Getting started
Create a solution
File → New Solution...
A solution is a container for related projects. It is stored as a .sspec
JSON file in the solution folder.
You will be asked how the team will share the work: a shared file system or GitHub. That choice changes which Git commands appear later, so answer it for how your team actually works. You get the same question if you begin with New Project... and create the solution on the fly — it is not skippable.
Create a project
File → New Project...
A project is one folder of specifications with its own Git repository —
git init runs when it is created. A solution can hold several.
Every new project starts with a Java.specconfig describing how to generate
code: Java, JUnit, tests into src/test/java/spectable, production stubs into
src/main/java/production. If you work in another language, edit that file or
add a second one beside it; see Configuration.
Add a specification
File → New File... (Ctrl+N), or right-click a project in the Solution
Explorer → New File...
Give it a .spectable extension to get the specification editor.
Open existing work
- File → Open Solution/Project...
- File → Recent Solutions
- File → Clone an Existing Solution... — clone a team repository and open it
The workspace
The layout follows Visual Studio. Every panel is on the View menu, so anything you close can be brought back.
| Panel | What it shows |
|---|---|
| Solution Explorer | Projects and their files |
| Symbol Tree | Entities, Collections, DataTypes and Attributes in the current file |
| Attribute Inspector | Fields of the attribute set under the cursor |
| Output | Build output, analysis results, and search results, in tabs |
| Editor tabs | Open files |
Solution Explorer ordering is deliberate: .spectable files first, then
.md, then everything else, with .specconfig files last. What you edit most
sits at the top.
By default the Explorer hides generated output — anything inside a config's
outputDirectory. View → Show All Files reveals it.
Other view controls:
- View → Refresh (
F5) — re-scan the solution after changing files outside AlignThree - View → Split Editor Right (
Ctrl+\) — two files side by side;Ctrl+Shift+\closes it
Writing a specification
A .spectable file is plain text. Blocks start at column one with a keyword;
tables are pipe-delimited rows; # starts a comment.
Specification
The file header:
Specification Shopping Cart
Attributes — a named set of fields
Attributes Adder
| Name | Default | Datatype |
| number1 | 0 | Integer |
| number2 | 0 | Integer |
| result | 0 | Integer |
Entity — a domain object
Entity CatalogItem
| Attribute | Type | Default | Notes |
| Name | SimpleText | NoName | |
| Price | Dollar | 1 | |
Collection — many of something
Collection Catalog
| DataType | Minimum | Maximum | Notes |
| CatalogItem | 0 | 10000000 | Each is unique |
DataType — a value with its own validity rules
DataType StandardID
Description Used to identify accounts and other items
Details Must be three digits, dash, three digits
Examples: ValidValues
| Value | IsValid | Notes |
| 123-456 | Yes | |
| 123-45 | No | |
ValidValues and EnumerationValues are built-in attribute sets for exactly
this purpose.
Built-in types — thirteen, needing no declaration:
Character |
String |
Text |
Boolean |
Integer |
Float |
Decimal |
Scientific |
Date |
Time |
DateTime |
Duration |
YesNo |
Anything else must be a DataType, Entity or Collection you declared.
ValidValues and EnumerationValues are built-in attribute sets, described
under DataType.
All thirteen are coloured in the editor, offered in the Type column, and accepted by Analyze and by every language generator.
Define — a named example row
Define ABillingAddress =
| Street | City | State | ZIP |
| 1 Apple Lane | Somewhere | NC | 27705 |
Refer to it later with =ABillingAddress. Defines keep long tables out of
scenarios and give recurring examples a name worth reading.
A Define can also name one value: Define TBR = -1 # not yet rolled. Then
=TBR may stand in any cell -- a step table, an Examples table, or the
Default column of an Attributes table -- and is read as though -1 had been
written there. The # comment is not part of the value.
Several constants are better said once, as a table. A bare Define with no
name, followed by a table with Name and Value columns, is one Define per
row -- a Notes column, or any other, is documentation:
Define
| Name | Value | Notes |
| TBR | -1 | Roll has not occurred |
| TBS | -1 | Score not yet computable |
Scenario — behaviour, as Given / When / Then
Scenario Add items
Given item collection is : OrderItemCollection
| Name | Price | Quantity | ItemTotal |
When item added : OrderItem Vertical
| Name | Widget |
| Quantity | 2 |
Then item collection is : OrderItemCollection
| Name | Price | Quantity | ItemTotal |
| Widget | $10.00 | 2 | $20.00 |
Step keywords: Given, When, Then, And, WhenThen.
The text after : names the attribute set describing the table's shape, and may
be followed by any number of modifiers, in any order.
| Modifier | What it says |
|---|---|
Vertical |
The table is transposed — attributes down the left, one column per instance. Reads better when a table has one row and many columns. Optional: a table whose first column is all attribute names is read this way whether or not the word is there. |
CompareOnly |
Equality is limited to the columns actually shown; the rest are filled with ?DNC? and not compared. |
EveryCell |
The table is a grid and each cell holds the text form of the named type, rather than being one row per instance with a column per attribute. |
They answer different questions, so they combine:
: Order Vertical CompareOnly is a transposed table checking only the
attributes it names.
EveryCell is how a table of whole objects is written:
Given the ingredients are : Ingredient EveryCell
| Sugar 200 | Butter 250 |
| Salt 5 | Yeast 7 |
That is four Ingredients. Each cell is read by the type's own text form — values
space separated, a value containing a space in double quotes, a nested block in
single quotes — which is the same form the object prints. A Define may supply
a cell: =Rent expands before the cell is converted. A table-form Define
cannot, because it is rows rather than one value.
Docstrings — a block of text as a step's argument
Some steps take a paragraph rather than a table. Put """ on its own line
directly under the step, and again to close:
Scenario Define with a String
Given this string
"""
This is
a multiline
string
"""
Then should be equal to string
=EQUAL_STRING
The step's glue method receives the whole block as one string, so this is how you hand a step an email body, a JSON payload, a rendered report.
Three rules the parser enforces:
- The step must be bare — no
: AttributeSetand no table under it. A step cannot take both a table and a docstring. """must be alone on its line. The opening"""sets the indentation: that much leading whitespace is stripped from every line inside, so you can indent the block to match the surrounding text without it appearing in the value.- Only inside
Scenario,BackgroundorCleanup.
A Define can hold one too, which is how the same text is reused as an
expectation:
Define EQUAL_STRING =
"""
This is
a multiline
string
"""
Edit String... (Ctrl+Shift+Q) opens the block in a proper text box rather
than making you edit it inside the specification.
BusinessRule and Calculation — a rule stated by example
BusinessRule Total Cart Price
Description How to Apply Discount and Shipping
Examples: CartInput
| TotalItems | Shipping | Discount | Total Price | Notes |
| $110 | $5 | $11 | $104 | Discount applied before shipping |
| $80 | $5 | $4 | $81 | |
Each Examples: row becomes a test case. Calculation behaves the same way
and reads better for pure arithmetic. A step can invoke one with
applying <RuleName>.
A rule is often clearer as a grid than as a sentence. Any named comment --
Description, Details, Notes, Constraint, Uses -- may be followed
directly by a table, and the table is part of the comment:
Details The discount bands are
| From | To | Rate |
| 0 | 99 | 0% |
| 100 | | 10% |
Nothing reads it and nothing tests it, which is what makes it different from
the Examples: table below it. The one exception is a block that is waiting
for its own table -- an Attributes header, a step naming a set, an
Examples: line -- which takes the table even with a comment in between.
Background and Cleanup
Background runs before every scenario in the file; Cleanup runs after.
Other blocks
| Keyword | Purpose |
|---|---|
ScenarioGroup |
Group related scenarios |
DomainTerm |
Define vocabulary |
Import |
Make another .spectable's Attributes and Define blocks visible |
Insert |
Splice another file's contents in — see below |
Description, Details, Constraint and Uses are named comments — prose
that travels with the block and is carried into the generated code.
Insert — pull a file's contents in
Insert splices another file's contents into the specification at the point it
appears, before anything is generated. The file name may be written
Insert "name", Insert 'name' or Insert <name> — all three currently resolve
relative to the folder holding the .spectable file.
Import is the other one, and they are not alike: Import makes another file's
Attributes and Define blocks visible by reference. Insert copies text in.
Where it goes changes what it means. There are three positions.
A CSV as a step's table. Put Insert where the table would go, under a step
that names an Attributes set:
Scenario An include of CSV file
Given a table : CSVContents
Insert "TestFolder/TableExample.csv"
The CSV becomes the step's table. Quoted fields survive — a,"b,c",d is three
values, not four. The column names are checked against the Attributes set, so
mistakes are caught rather than silently generating the wrong test:
ERROR:3:Table is missing column 'B' and 'B' has no default value
WARNING:3:Table has column 'Z' which doesn't match any field on 'CSVContents' — it will be ignored
Any file inside a docstring. Between """ lines, Insert is replaced by the
file's contents:
Given a string include
"""
Insert "string.txt"
"""
A .csv or .tsv here is still converted to a table; anything else is inserted
as literal text. Leading whitespace up to the column of the opening """ is
stripped, as it is for text typed inline.
A whole .spectable at the top level. The inserted file's declarations —
Entity, Attributes, Scenario, BusinessRule and the rest — are spliced in
and parsed as though typed in place:
Specification Host
Insert "shared_entities.spectable"
Two things to know about this form:
- The inserted file's
Specificationline is dropped only if the host file has one of its own. If the host has noSpecification, the inserted one is used, and it names the generated test class —Insert "part.spectable"into a file with noSpecificationproducesInserted_Part_Test, not something named after the host. Give the host its ownSpecificationand this stops being a surprise. - The same file is spliced only once. A second
Insertof it is skipped, which is also what stops two files that insert each other from looping.
Insert in a position that is not one of those three does nothing, and says
nothing. It is treated as a comment keyword, so there is no warning and no
error. The one to watch for is a CSV at the top level:
Specification Host
Insert "data.csv" <- silently ignored; a CSV needs a step or a docstring
A file named by an Insert that does not exist is reported wherever the
Insert stands -- Inserted file not found: 'data.csv' -- by Analyze and by the
build alike. So is an Import of a file that is not there.
Tags
Tags label a block so it can be selected later — to run only the smoke tests, or to keep unfinished work out of the build. There are two kinds, and the difference matters.
@Tag reaches the generated tests. It becomes whatever the target
framework uses to categorise a test — a JUnit 5 @Tag("smoke"), for instance.
Use it when your test runner will do the selecting.
$Tag never leaves AlignThree. It exists only so the generator can decide
whether to emit the block at all. Nothing about it appears in the output.
@smoke @checkout
$wip
Scenario Add items
Given item collection is : OrderItemCollection
| Name | Price | Quantity | ItemTotal |
Several tags go on one line or on separate lines, and they apply to whatever
block comes next — Scenario, BusinessRule, Calculation, DataType, or the
Specification line itself, where they apply to everything in the file.
Tags must sit immediately above their block. A blank line between the tag and the block discards it, silently. This is deliberate — it is what stops a stray tag at the top of a file from attaching itself to the first scenario that happens to follow — but it does mean a tag separated by whitespace does nothing at all.
Filtering what gets generated
Set tagFilter in the .specconfig to a boolean expression over the $ tags.
Only matching blocks are generated; an empty filter generates everything.
smoke only blocks tagged $smoke
NOT wip everything except unfinished work
smoke AND NOT wip
(smoke OR regression) AND NOT draft
AND, OR, NOT and the tag names are all case-insensitive, and parentheses
group as you would expect.
Filtering happens at generation time, so a filtered-out scenario produces no
test and no glue stub. That is the point — $wip on a half-written scenario
keeps it in the specification, where the conversation about it continues,
without breaking the build.
Working with tables
Most editing effort in a specification is table editing, so the editor is table-aware. Right-click inside a table:
| Command | Effect |
|---|---|
| Insert Row Below / Delete Row | Row editing |
| Insert Table Header | Add a header row |
| Transpose Table | Swap rows and columns |
| Import CSV... | Replace the table from a CSV file |
| Extract as AttributeSet... | Turn the table's columns into a named Attributes block |
| Extract as Define... | Turn the selected row into a named Define |
| Create Attributes 'X' | Declare an attribute set you referenced but never wrote |
And from the Edit menu:
- Format Table (
Ctrl+Alt+F) — align the pipes - Edit Table... (
Ctrl+Shift+T) — edit in a spreadsheet-style grid, with row and column context menus - Edit String... (
Ctrl+Shift+Q) — edit a long cell value in a proper text box instead of squinting at a table row
Seeing what you wrote
These are the features to reach for when a specification does not behave the
way you expected. All are on the right-click menu in a .spectable file, and
each stays open and refreshes as you type.
Simulate Scenario... — put the cursor in a scenario and see it expanded as
it will actually run: Background steps included, =Define references
resolved, Vertical tables turned the right way round. This is the fastest way
to catch a scenario that reads correctly but does not say what you meant.
Run Examples... — put the cursor in a BusinessRule, Calculation or any
Examples: block and check every row against the declared types before
generating anything. Cells are flagged as valid, wrong type, missing, or
"does not compare".
Display Background... / Display Cleanup... — show what runs around every scenario in the file.
Spelling
A specification is mostly words, and a misspelled one is underlined in red as
you type -- in step text, names, comments and table cells alike. A camel-cased
name is checked one word at a time, so TotalScore passes and TotlScore is
marked at Totl. Anything written entirely in capitals (TBR, JSON),
anything with a digit in it, file names inside quotes, and the contents of a
docstring are left alone.
A name the specifications declare is spelled the way it is declared: an
Entity, Attributes, DataType, DomainTerm, Define, Collection, BusinessRule or
Calculation name, and every attribute name, is never queried. Words joined by
_ or - are checked one at a time.
Right-click an underlined word for the likely spellings; pick one and it
replaces the word. Add '...' to Dictionary accepts a word for good, in every
file, for you; Add '...' to Solution Dictionary accepts it for everyone who
opens the solution. Right-click a word you added for the matching Remove.
Your own words are kept in user words.txt in the application's data folder;
the solution's are in dictionary.txt beside the .sspec, one word per line,
meant to be committed with the specifications so a team shares its
vocabulary. Both are plain text and can be edited by hand.
Edit → Check Spelling switches the whole thing off and on. The dictionary is American English (en_US), compiled into AlignThree.
Navigating and renaming
Right-click anywhere on a line — on a symbol, on the step text, inside a table:
- Go to Definition
- Find All References
- Show Attributes: X / Show Define: X — peek without leaving your place
- Find Step Usages — every scenario using this step
- Rename Symbol: X...
- Rename Step: X...
The symbol entries appear only when the word under the cursor is one AlignThree knows; the step and table entries appear whenever the line qualifies, wherever on it you clicked.
Rename Symbol renames an entity, collection or attribute set across every
.spectable file in the solution, commits the change, and updates your glue
files in all nine languages — replacing XString, XTyped and the bare name.
Rename Step — from the context menu or Edit → Rename Step... (F2) —
asks once for the new text, pre-filled with the current step. It replaces the
text across the solution, commits, and renames the corresponding glue methods.
A glue method is a mangled identifier derived from the step — When_item_added
in Java, when_item_added in Python, WhenItemAdded in Go, whenItemAdded in
Swift and JavaScript — so replacing the step text alone would never reach it.
AlignThree derives the name in every shape and renames whole-word matches, then
reports how many it changed. F2 uses the step the caret is on; select text
first if you want to rename only part of one.
Neither command rebuilds anything — a build can take a while, and when to spend
that time is your call. Both tell you which projects need one, because the
generated _Test files keep referring to the old names until they are
regenerated. Your glue and production code is never overwritten by that build.
Edit → Find All Usages... (Shift+F12) searches the whole solution;
results land in the Output panel, and double-clicking one jumps to it.
Configuration
A .specconfig file at the project root tells AlignThree how to generate code.
A project may hold several — one per language — and you pick the active one
from Build → Configuration. Opening a .specconfig gives you a form, not
raw JSON.
| Setting | Meaning |
|---|---|
language |
Java, CSharp, Python, Go, Rust, Swift, JavaScript, TypeScript, Cpp |
framework |
Test framework — see below |
outputDirectory |
Where generated tests go, relative to the config file |
namespacePrefix |
Namespace or package for generated code |
imports |
Extra import/using lines added to every generated file |
copySpectable |
Copy the source .spectable beside the generated code |
overwriteGlue |
Regenerate glue stubs even when they exist — off by default, and normally leave it off, since glue is where your code lives |
tagFilter |
Boolean $tag expression; only matching blocks are generated |
converterPath |
Empty means auto-detect next to AlignThree |
createProductionClasses |
Write production stubs for types that have none |
productionClassesDir / productionClassesPackage |
Where those stubs go |
failEveryTest |
End every generated glue stub with a failure, so a step that was never implemented cannot pass silently. On by default — turning it off means an unimplemented step reports success |
externalSpectables |
.spectable files from other projects whose types are visible here, each with its own production folder and imports |
Frameworks by language:
| Language | Frameworks |
|---|---|
| Java | JUnit, TestNG |
| C# | MSTest, NUnit, xUnit |
| Python | pytest, unittest |
| JavaScript / TypeScript | Jest, Vitest |
| Go | testing |
| Rust | builtin |
| C++ | GoogleTest |
| Swift | XCTest |
Generating code
| Command | Shortcut | Scope |
|---|---|---|
| Build → Current File | F6 |
The open .spectable |
| Build → Project | Shift+F6 |
Every specification in the project |
| Build → Solution | Ctrl+F6 |
Everything |
Output appears in the Output panel. Errors are clickable — double-click to land on the offending line.
Project and solution builds delete the generated *String and *Typed
classes first, then regenerate them. This is what removes files left behind by
a renamed or deleted type; without it, stale classes accumulate and break the
build with errors about types that no longer exist. Single-file builds do not
clear, so use a project build after renaming or deleting anything.
Your glue and production code is never touched by this.
What gets generated
<outputDirectory>/
common/ generated value classes — XString, XTyped, and an index
<Spec>_Test the tests
<Spec>_glue the glue — where you write code
production/ stubs for your production classes
common/ is fully generated. Never edit it; every build rewrites it.
Glue is yours. AlignThree creates each glue method once, as a stub, and thereafter only appends methods that do not exist yet. It never rewrites or removes what you wrote. The cost of that safety: glue for a step you deleted stays behind until you remove it -- so every build lists, as warnings, the public glue methods no generated test calls any more. Your own private helpers are not reported.
Each stub ends with a failure — fail(), Assert.Fail, raise NotImplementedError, t.Fatal, panic!, XCTFail, throw new Error or
ADD_FAILURE(), depending on the language — so a step you have not implemented
yet cannot report success. Write your code above it and delete the failure line
when the step is done. Clearing failEveryTest in the configuration omits
those lines, at the cost of a scaffold that passes green before anything is
implemented.
Production files are yours too, and are never overwritten. Before writing a
stub, AlignThree searches the production folder for a class, struct, enum,
interface, record, protocol or type of that name — anywhere in the folder, not
just in the file it would have created. If it finds one, it writes nothing and
logs an INFO line saying where the type already lives. So consolidating several
types into one file is fine, and so is renaming the file. Turn
createProductionClasses off and no production files are written at all.
Keep computation out of glue. Glue should drive production objects and compare results. If a total needs calculating, the production class calculates it. Glue that does arithmetic is testing itself.
Testing an API
A specification can drive a running service instead of a class. Nothing in the language is special-cased for it — the tables, the steps and the generated readers are the ordinary ones. What changes is that there is no production code: the service is the thing under test, so the glue is all there is.
AddressCorrection.spectable in the
SpecStudioExampleTests
repository is a worked example of everything below, running against a public
service that needs no API key.
The shape
Three attribute sets carry a call. One describes the request, one the status, one the response body:
Attributes Request
| Attribute | Type | Default | Notes |
| Method | String | | GET, POST, PUT, PATCH, DELETE |
| Page | String | | Appended to the base Page from the Background |
| Parameter | String | | Appended after the Page, such as an id |
| Body | String | | Name of the attribute set holding the body |
Keep the base URL in the Background so a scenario says only what is peculiar
to it, and build the URL from the pieces — posts/1 is Page posts with
Parameter 1. Joining the parts in glue rather than writing whole URLs in
cells means a call with no Parameter cannot end up with a trailing slash.
Background
Given base Page is : String
| https://api.example.com/address |
Check the status and the body separately
Then response status is : Status Vertical
| Code | 200 |
And response body is : Corrected Vertical
| Street | 1 Penny Lane |
This is worth doing even though one step would parse. A status field sitting in
the same attribute set as the body's fields makes the generated reader demand a
status field that no response body contains — it is the HTTP status code, not
part of the JSON — and the glue then has to splice one in before every check.
Kept apart, the body goes straight through fromJsonValue with nothing added.
It also separates two different failures. A 404 with the right body and a 200 with the wrong one are not the same defect, and they now fail on different lines.
What the glue does
Three jobs, and only three:
- Turn the request table into JSON with the generated
toJSON() - Call the service
- Turn the response back with the generated
fromJsonValue(), then compare
Put the transport — URL assembly, verb selection, carrying the payload — in one
static helper beside the glue rather than in common/, which every build
rewrites. Have it throw, naming the URL, when the service cannot be reached: a
call that never happened is a broken test, not a failed assertion, and the
message should say which.
That leaves glue that does no computation, which is the same rule as everywhere else. Here it is easier to keep, because there is nothing to compute.
Write real expected values
A cell reading string or number asserts almost nothing. Any non-empty
text passes, and the scenario reads as covered while checking that a field
exists. Placeholders are what you write before you have called the service —
they should not survive the first run.
If the service returns the same answer every time, put that answer in the cell:
And response body is : Corrected Vertical
| Street | 1 Penny Lane |
| Zip | 62701-1032 |
| Quality | Verified |
1 Penny LN becoming 1 Penny Lane is the reason the service exists. 62701
coming back as 62701-1032 is a rule the caller is entitled to. Both are worth
stating; neither survives a cell reading string.
When a value genuinely varies — a generated id, a timestamp — say so in a comment where it occurs, so the next reader knows the placeholder was a decision rather than an omission.
Checking part of a response
CompareOnly limits equality to the columns actually shown, filling the rest
with ?DNC?. Use it when the scenario is about one rule:
And response body is : Corrected CompareOnly
| Street | Quality |
| PO Box 12 | Verified |
Adding a field to the response does not break that scenario.
A step may carry both modifiers, in either order. They answer different
questions — Vertical is how the table is laid out, CompareOnly is which of
its columns are compared — so : Corrected Vertical CompareOnly states one
instance down the page and checks only the attributes it names. Writing the
table horizontally is still how several rows are checked at once:
And response array items match : Corrected CompareOnly
| Street |
| 1 Penny Lane |
| 1 Penny Court |
Write ?DNC? in a cell directly to skip one field while checking its
neighbours — useful when a call legitimately succeeds without producing a
value, such as an address that cannot be corrected coming back 200 with the
corrected fields unset.
Analyze
Analyze → Solution (Shift+F7), or Analyze → Project for one project.
Analysis reads every specification together and reports what no single file can show. Among the checks:
- Unknown references — an
AttributeSet,Entity,DataType,BusinessRuleorCalculationthat is used but never declared, and=Valuereferences with no matchingDefine - Duplicates — repeated
Scenario,SteporDatanames, and aDomainTermcolliding with a built-in or declaredDataType - Table shape — a row whose column count does not match its header
- Missing pieces — a
DataTypewith no data table orExamples:section; a step with a data table but no attribute set naming its columns; a step naming an attribute set with no table under it - Misuse — an unrecognized keyword or step modifier, or a
Cleanupblock containing anything butThenandAnd - Files — an
ImportorInsertpointing at a file that is not there - Values — a cell, an
Examples:row or aDefaultthat its column's built-in type cannot read:fourin anIntegercolumn,25,200.00in aDecimalone
Analyze and the build read a specification the same way. Every check about one file's contents runs on the parse tree the generators use, and the table and value checks are the very functions the converter runs before it generates -- so a finding here is the finding the build would make, and the build's warnings are these.
Results fill the Analysis tab; double-click one to jump to it.
Analyze saves modified editors first and restores your cursor position and scroll position afterwards, so it is safe to run mid-edit.
Sharing work
The Git menu reflects the sharing mode chosen when the solution was created.
Shared file system — a single Share with Git... item.
GitHub mode:
| Command | Effect |
|---|---|
| Commit and Push... | Asks for a reason for the change, then commits and pushes |
| Fetch | Fetch without merging |
| Get Others' Changes | Pull teammates' work; conflicts open a resolution dialog |
| Repository Settings... | Remote URL, branch, credentials |
Comparing with an earlier version
Analyze → Diff Current File → Against Previous Version (Ctrl+D), Against Two
Versions Back, or Choose Version... shows the whole file as it is now
against the whole file as it was, in the Diff tab. A version is a save that
changed this file, listed by date and by the reason given at push time.
Two ways of looking, on a toggle at the top of the tab:
- Inline — one document, the way Word compares: what went away is struck through in red, what arrived is underlined in green, and a line with one change shows it in place, word by word, so a table row keeps its shape and its neighbours. A line that changed in more than one place is shown whole: the old line struck through, the new one underlined beneath it.
- Side by side — the earlier version on the left, the current one on the right, scrolled together, a changed line beside its replacement.
Revert to This Version loads the earlier version into the editor, unsaved: one Undo puts it back, and nothing is written until you save.
Every save also commits. File → Save writes the file and then commits that
project with the message Auto-save. Your specification history is complete
without any effort, and the reason you give at push time is what your teammates
actually read.
Credentials are stored per project. AlignThree supplies them to Git through a
helper program rather than embedding them in the remote URL, so tokens do not
end up in .git/config or in the output panel.
Settings
File → Settings...
| Tab | Contents |
|---|---|
| General | Automatically reload files changed outside the editor |
| Editors | External program per file extension — blank means the built-in editor |
| FeatureX | Implicit Data import across folders; unique Scenario names; unique Step names; step suggestion scope |
| Appearance | Dark theme |
| Fonts | Editor font |
The Editors tab is how .csv, .xlsx and .md files open in a real
spreadsheet or Markdown editor. Leave an extension blank and AlignThree edits it
as text.
Keyboard reference
File
| Action | Key |
|---|---|
| New File | Ctrl+N |
| Open File | Ctrl+O |
| Save | Ctrl+S |
| Save All | Ctrl+Shift+S |
Ctrl+P |
Edit
| Action | Key |
|---|---|
| Find | Ctrl+F |
| Replace | Ctrl+H |
| Go to Line | Ctrl+G |
| Find All Usages | Shift+F12 |
| Rename Step | F2 |
| Format Table | Ctrl+Alt+F |
| Edit Table | Ctrl+Shift+T |
| Edit String | Ctrl+Shift+Q |
View, Build, Analyze, Git
| Action | Key |
|---|---|
| Refresh | F5 |
| Split Editor Right | Ctrl+\ |
| Close Split | Ctrl+Shift+\ |
| Build Current File | F6 |
| Build Project | Shift+F6 |
| Build Solution | Ctrl+F6 |
| Analyze Solution | Shift+F7 |
| Diff Current File | Ctrl+D |
Mouse, in a .spectable editor
| Action | Gesture |
|---|---|
| Select a word | Double-click it |
| Select a whole block | Double-click the block's keyword, or triple-click any line in it |
| Context menu for what is under the pointer | Right-click |
Selecting a whole block is meant for cut and copy: the selection runs from the
header line through the block's last non-blank line and includes the line break,
so Ctrl+X lifts the block out cleanly and Ctrl+V puts it back as whole lines.
Blank lines between blocks are left where they are, as separators.
A block ends at the next block keyword, not by indentation — specifications are
normally written flush left, so the steps and tables under a Scenario share its
column. Text inside a """ docstring is part of the block that owns it even when
it contains something that looks like a keyword, which is what makes
Insert "file.txt" inside a docstring safe to select across.
The double-click is bound to the keyword only — the Scenario in
Scenario Add two numbers, not the whole header line. Double-clicking the
block's name still selects a word, as it does everywhere else.
A first pass, end to end
- File → New Solution..., choose a sharing mode.
- File → New Project... — you get
Java.specconfig. Edit it if you work in another language. - File → New File..., name it
Calculator.spectable. - Write a
Calculationwith anExamples:table and theAttributesblock describing its columns. - Right-click in the examples table → Run Examples... and confirm every row is valid.
- Build → Project (
Shift+F6). - Open the generated
_gluefile and implement the stubs against your production classes. - Run the tests the way you normally would — from your IDE, or with your
language's runner (
mvn test,dotnet test,pytest,go test,cargo test,npm test,swift test,ctest). - Change the specification, build again, re-run. Only the glue for genuinely new steps needs writing.
Related documents
Getting Started.md— the first hour, if you have not written one beforespectable syntax v3.3a.md— the language referenceConfiguration Guide.md— every.specconfigfield, keeping specifications in a different repository from the generated code, and the JSON supportGit Setup.md— the one-time GitHub sign-in setup, for whoever installs itBuilding Distributions.md— packaging and signing AlignThree itself- SpecStudioExampleTests —
worked specifications generated into all nine languages, including
AddressCorrection.spectable, the example behind Testing an API Remaining Work.txt— the project backlogarchive/— superseded syntax revisions, design notes and earlier backlogs, kept for history. Nothing there is current.
User Guide.md
All documentation