Foundation

Concept Simplification

Why LLLTS removes overlapping language choices and keeps one canonical form when possible.

One canonical form when possible

TypeScript and JavaScript often offer several ways to express nearly the same job. That is useful for humans writing by hand, but it also creates branching: the reader or the model has to choose between overlapping constructs before it can focus on the actual domain problem.

Reject duplicated concepts. Keep one strong default when extra variants mainly add decision load.

That is the simplification idea in LLLTS. The language does not try to remove power for novelty. It tries to remove duplicate or overloaded choices when one canonical form can still do the work. The result is lower concept count, lower ambiguity, and more attention available for user intent, domain modeling, structure, and verification.

Why this helps LLMs

Fewer equally plausible constructs means fewer syntax branches to consider. The model can spend more of its limited working attention on the subject of the code instead of on choosing between several near-duplicate language forms.

Structural simplifications

The clearest examples happen at the project and file level. Ordinary TypeScript is comfortable with free functions, loose constants, extra helper interfaces, and several primary concepts in one file. LLLTS collapses those options into one main structural path.

Rejected ts
const cache = new Map<string, User>();

function loadUser(id: string): User {
  return new User(id);
}
Allowed ts
export class UserRepository {
  static readonly cache = new Map<string, User>();

  @Spec("Loads a user by id.")
  static loadUser(id: string): User {
    return new User(id);
  }
}

The same idea shows up in type modeling. If you need a pure data contract, prefer a type. If you need shared runtime behavior or overridable methods, use an abstract class. The point is not "never express a contract." The point is to avoid carrying interface, type alias, free function, and loose constant patterns all at once when one canonical choice is enough.

Common job Canonical LLLTS form
Primary runtime behavior class in its own filename-matched file
Pure data shape type
Free helper class method, usually static
Shared top-level constant class property, usually static readonly
Bootstrap action a final top-level new ClassName() only where explicitly allowed

Expression and guardrail simplifications

Some simplifications are not about duplicate declarations. They are about overloaded meaning. JavaScript lets conditions, comparisons, arithmetic, and nullability shortcuts carry several different interpretations. LLLTS narrows those positions to one explicit reading.

Rejected ts
if (userName) {
  publish();
}

if (status == 0) {
  reset();
}

const delta = "5" - 2;
return user!.name;
Allowed ts
if (userName !== "") {
  publish();
}

if (status === 0) {
  reset();
}

const parsedCount = Number.parseInt("5", 10);
const delta = parsedCount - 2;

if (user === undefined) {
  throw new Error("Expected user");
}

return user.name;

In LLLTS, boolean positions should mean boolean, arithmetic should mean numeric intent, and nullability claims should be proven in code instead of asserted by punctuation. This keeps the meaning of those positions stable for both humans and models.

Testing-workflow simplifications

Testing gets the same treatment. Instead of letting projects invent many layouts for tests, host-selection rules, and scenario signatures, LLLTS keeps one companion model.

Rejected ts
import { describe, it, expect } from "vitest";

describe("UserService", () => {
  it("loads a user", async () => {
    expect(await loadUser("42")).toBeDefined();
  });
});
Allowed ts
import "./UserService.lll";
import { Scenario, Spec, type ScenarioParameter } from "../../public/lll.lll";

@Spec("Exercises UserService scenarios.")
export class UserServiceTest {
  testType = "unit" as const;

  @Scenario("Loads a user.")
  static async loadsUser(scenario: ScenarioParameter): Promise<void> {
    scenario.assert(true, "Replace with host-class assertion.");
  }
}

This is an intentional narrowing. It gives up some layout freedom so the language, the compiler, and the model all agree on one discoverable way to find tests, name them, run them, and classify them as "unit" or "behavioral".

Not every rule is concept reduction

It would be too broad to claim that every `LLLTS` restriction exists to remove duplicates. Some rules are there for local reasoning and verification discipline instead.

  • @Spec("...") makes purpose visible before the body begins.
  • Explicit return types make contracts readable from signatures alone.
  • File, method, and folder limits keep complexity density bounded.

These rules complement simplification, but they are better described as boundedness and auditability rules rather than as concept elimination.

Rejected simplifications and boundaries

The simplification bar is not "ban syntax because it looks different." The stronger test is: does one canonical form really cover the job without special-case exceptions?

A good example is backticks-only strings. The idea sounds attractive, but static ECMAScript imports still require quoted module specifiers, and TypeScript ambient module declarations do too. That would force exceptions such as:

import "./ClassName.lll";
or
declare module "pkg" 

Once exceptions appear, the rule stops simplifying the language. So LLLTS rejects that idea and keeps delimiter choice aligned with valid underlying TypeScript and JavaScript syntax.

The broader result

The practical claim is modest but important: when the language removes duplicated concepts and narrows overloaded ones, both humans and models have less syntax branching to manage. That leaves more room for the real work of software: naming the right concepts, giving them stable structure, and verifying behavior through scenarios.