npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thezelijah/majik-runway

v1.0.1

Published

Majik Runway is a comprehensive financial runway, cashflow, and burn-rate modeling engine in the Majik system.

Downloads

204

Readme

Majik Runway

It helps founders, operators, and finance teams understand how long a business can operate, where money is going, and how decisions affect survival and growth.

Majik Runway models your business as a living financial system, not a static spreadsheet.

It composes other Majik financial primitives such as MajikProduct, MajikService, MajikSubscription, and MajikMoney to produce accurate, period-based projections.


Live Demo

Majik Runway Thumbnail

Click the image to try Majik Runway live.


Table of Contents


Overview

Majik Runway manages:

  • Cash Balance: Starting cash, funding events, and cumulative cash over time
  • Revenue Streams: Products, services, and subscriptions
  • Expenses: Fixed, variable, and one-time costs
  • Burn Rate: Monthly and trend-based burn
  • Runway: Estimated months before cash depletion
  • Period Modeling: YYYYMM-based projections with proration
  • Scenario Planning: Live recomputation on assumption changes
  • Serialization: Safe export/import using MajikMoney

Majik Runway is not accounting software — it is a forecasting and decision engine.


Key Concepts

Majik Runway is a financial simulation and planning engine, not an accounting system. It models cash behavior over time using deterministic rules so founders, operators, and finance teams can reason about runway, burn, growth, and survivability with clarity.

Modeling Period

All computations occur inside a defined modeling window:

  • Start: YYYYMM
  • End: YYYYMM

This period defines the timeline of the simulation, not the lifetime of the business.

Key rules:

  • All revenues, expenses, funding events, and taxes are normalized to monthly granularity
  • Partial months are prorated
  • Items outside the modeling period are ignored
  • Results are computed month-by-month, then aggregated

Majik Runway assumes time moves forward in discrete monthly steps. There is no backdating, retroactive mutation, or implicit carry-over beyond defined rules.


Revenue Streams

Majik Runway does not invent revenue logic — it reuses Majik domain models:

  • MajikProduct
  • MajikService
  • MajikSubscription

Each revenue stream:

  • Has an activation window (start / end)
  • Produces gross monthly revenue
  • May include:
    • Pricing rules
    • Quantity / capacity limits
    • Growth or churn assumptions
  • Can scale, pause, or terminate independently

Important clarifications:

  • Revenue is modeled as earned revenue, not necessarily collected cash
  • Cash collection timing (e.g. delayed payments) must be modeled explicitly if needed
  • Refunds, chargebacks, or revenue leakage must be modeled as negative revenue or expenses

Expenses

Expenses represent cash outflows that reduce available cash and contribute to burn. Majik Runway models expenses explicitly and intentionally avoids accounting abstractions such as depreciation or accruals.

Supported Expense Types:

Operating Expenses

Recurring costs required to keep the business running:

  • Salaries and contractors
  • Rent and utilities
  • Software subscriptions
  • Base infrastructure costs

These expenses are typically predictable and stable over time.

Variable Expenses

Costs that scale with activity, usage, or growth:

  • Marketing spend
  • Transaction fees
  • Usage-based infrastructure
  • Performance-based commissions

Variable expenses may fluctuate month-to-month and often correlate with revenue or volume.

Capital Expenses

Large, usually strategic cash outlays intended to provide long-term value:

  • Equipment and hardware
  • Initial platform build costs
  • One-time tooling or setup investments

Capital expenses are treated as immediate cash outflows, not depreciated assets.

Expense Timing

Independently of type, an expense may follow one of these timing behaviors:

One-time

Occurs once in a specific month

Recurring

Occurs every month within an active period

Capital

Large one-time expenditures, typically paired with ExpenseType.Capital

Majik Runway treats all expenses as cash-paid in the month they occur. If amortization, deferred payments, or financing effects are required, they must be modeled explicitly.

Rules:

  • Expenses reduce cash balance
  • Expenses contribute directly to burn
  • Expenses are assumed to be paid in the month they occur
  • Depreciation, amortization, and accruals are out of scope

Majik Runway intentionally favors cash realism over accounting abstraction.


Funding

Funding represents external capital injections that increase available cash without generating revenue.

Supported Funding Types:

Equity

Capital raised in exchange for ownership (e.g. angel or VC funding).

  • Increases cash balance
  • Does not count as revenue
  • Does not affect profit or margins
  • Equity dilution is out of scope
Debt
  • Borrowed capital that must be repaid.
  • Increases cash balance at issuance
  • Principal repayment and interest are not implicit
  • Interest and repayments must be modeled explicitly as expenses
Grant

Non-dilutive capital typically provided by institutions or programs.

  • Increases cash balance
  • Does not count as revenue
  • Assumed to have no repayment obligation
  • Funding Modeling Rules
  • Funding occurs at a specific YYYYMM
  • Funding increases cash only
  • Funding does not reduce burn directly
  • Funding does not imply future obligations unless explicitly modeled

Majik Runway intentionally separates capital flow from operational performance.

Relationship to Burn & Runway

  • Expenses and taxes increase burn
  • Revenue and funding reduce burn
  • Funding extends runway without improving profitability
  • Capital-heavy strategies may show strong cash early but poor burn efficiency

Burn Rate

Burn rate represents net cash loss per month.

Formula (simplified):

Burn = Cash Outflows − Cash Inflows

Where:

  • Outflows include expenses and taxes
  • Inflows include revenue collections and funding

Key distinctions:

  • Burn is a cash metric, not a profit metric
  • A company can be profitable but still burn cash
  • A company can have revenue and still burn aggressively

Cash Flow

Cash flow is tracked explicitly per month:

Formula (simplified):

Closing Cash = [Opening Cash] + [Cash Inflows] − [Cash Outflows]

There is no hidden balancing or correction layer.

If cash goes negative:

  • The model reflects insolvency
  • Runway is considered exhausted

Runway

Runway answers a single question:

How many months can the company survive before cash reaches zero?

Runway is derived from:

  • Current cash balance
  • Forward-looking burn trajectory
  • Planned funding events

Runway is dynamic, not static — it changes as assumptions change.


Profit vs Cash (Important)

Majik Runway makes a deliberate distinction:

| Concept | Meaning | | ------- | ------------------ | | Revenue | Value earned | | Profit | Revenue − Expenses | | Cash | Money available | | Burn | Net cash loss |

Majik Runway optimizes for cash truth, not accounting compliance.


What Majik Runway Is (and Is Not)

It is:

  • A financial planning engine
  • A runway and burn simulator
  • A decision-support tool

It is not:

  • An accounting system
  • A tax filing tool
  • A replacement for a CFO

Installation

npm i @thezelijah/majik-runway @thezelijah/majik-money @thezelijah/majik-product @thezelijah/majik-service @thezelijah/majik-subscription

Quick Start

import { MajikRunway } from "@thezelijah/majik-runway";
import { MajikMoney } from "@thezelijah/majik-money";
import { BusinessModelType, VATMode } from "@thezelijah/majik-runway/enums";
import { RevenueStream } from "@thezelijah/majik-runway/revenue";
import { ExpenseBreakdown } from "@thezelijah/majik-runway/expenses";
import {
  dateToYYYYMM,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";

const revenueStream = new RevenueStream("PHP");
const expenseBreakdown = new ExpenseBreakdown("PHP");
const fundingManager = new FundingManager("PHP");

const runway = MajikRunway.initialize({
  type: BusinessModelType.Hybrid,
  money: MajikMoney.fromMajor(1000000, "PHP"), // initial cash
  taxConfig: {
    vatMode: VATMode.VAT,
    vatRate: 0.12,
    percentageTaxRate: 0.03,
    incomeTaxRate: 0.08,
  },
  revenues: revenueStream,
  expenses: expenseBreakdown,
  funding: fundingManager,
  period: {
    startMonth: dateToYYYYMM(new Date()),
    endMonth: offsetMonthsToYYYYMM(dateToYYYYMM(new Date()), 23),
  },
});

Usage

Initialize a MajikRunway Instance

Majik Runway is initialized using a configuration object that defines your business model, starting cash, taxes, revenue streams, and expenses.

import { MajikRunway } from "@thezelijah/majik-runway";
import { MajikMoney } from "@thezelijah/majik-money";
import { BusinessModelType, VATMode } from "@thezelijah/majik-runway/enums";
import { RevenueStream } from "@thezelijah/majik-runway/revenue";
import { ExpenseBreakdown } from "@thezelijah/majik-runway/expenses";
import {
  dateToYYYYMM,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";

const revenueStream = new RevenueStream("PHP");
const expenseBreakdown = new ExpenseBreakdown("PHP");
const fundingManager = new FundingManager("PHP");

const runway = MajikRunway.initialize({
  type: BusinessModelType.Hybrid,
  money: MajikMoney.fromMajor(1000000, "PHP"), // initial cash
  taxConfig: {
    vatMode: VATMode.VAT,
    vatRate: 0.12,
    percentageTaxRate: 0.03,
    incomeTaxRate: 0.08,
  },
  revenues: revenueStream,
  expenses: expenseBreakdown,
  funding: fundingManager,
  period: {
    startMonth: dateToYYYYMM(new Date()),
    endMonth: offsetMonthsToYYYYMM(dateToYYYYMM(new Date()), 23),
  },
});

Business Models

The business model determines how revenue streams behave and how financial assumptions are applied.

  • BusinessModelType.Product
  • BusinessModelType.Service
  • BusinessModelType.Subscription
  • BusinessModelType.Hybrid

Hybrid models allow products, services, and subscriptions to coexist.


Period Configuration

Majik Runway operates on a monthly YYYYMM timeline.

import {
  dateToYYYYMM,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";

const period = {
  startMonth: dateToYYYYMM(new Date()),
  endMonth: offsetMonthsToYYYYMM(dateToYYYYMM(new Date()), 23),
};

runway.setPeriod(period);

This automatically updates the period across the revenue stream, funding, and expenses.

Changing the period automatically:

  • Reprojects revenue
  • Reallocates expenses
  • Updates burn & runway

Revenue Streams

Revenue streams are managed independently and injected into Majik Runway.

They may internally contain:

  • MajikProduct
  • MajikService
  • MajikSubscription

Revenue streams define:

  • Price
  • Volume
  • Growth
  • Start & end dates

Majik Runway does not dictate how revenue is computed — it only aggregates results.

Add/Manage Products to Revenue Stream

import { MajikProduct } from "@thezelijah/majik-product";
import { MajikRunway } from "@thezelijah/majik-runway";
import { MajikMoney } from "@thezelijah/majik-money";
import { BusinessModelType, VATMode } from "@thezelijah/majik-runway/enums";
import { RevenueStream } from "@thezelijah/majik-runway/revenue";
import { ExpenseBreakdown } from "@thezelijah/majik-runway/expenses";
import {
  monthsInPeriod,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";
// Create a new Product Instance

const newProduct = MajikProduct.initialize(
  "Brand Z Phone",
  ProductType.PHYSICAL,
  MajikMoney.fromMajor(24500, "PHP")
);

// Prepare and create COGS manually - make sure unitCost and subtotal are MajikMoney class instances (Use MajikMoney.fromMajor())
const productCOGS = [
  {
    id: "mjkpcost-vyR7lY7r",
    item: "Plastic",
    quantity: 50,
    unitCost: {
      __type: "MajikMoney",
      amount: "100",
      currency: "PHP",
    },
    unit: "PELLETS",
    subtotal: {
      __type: "MajikMoney",
      amount: "5000",
      currency: "PHP",
    },
  },
  {
    id: "mjkpcost-wKxE3TT6",
    item: "Metal Parts",
    quantity: 5,
    unitCost: {
      __type: "MajikMoney",
      amount: "2000",
      currency: "PHP",
    },
    unit: "G",
    subtotal: {
      __type: "MajikMoney",
      amount: "10000",
      currency: "PHP",
    },
  },
  {
    id: "mjkpcost-SMWmI7LU",
    item: "Glass Parts",
    quantity: 1,
    unitCost: {
      __type: "MajikMoney",
      amount: "10000",
      currency: "PHP",
    },
    unit: "PIECE",
    subtotal: {
      __type: "MajikMoney",
      amount: "10000",
      currency: "PHP",
    },
  },
];

// Set Cost of Goods and replace the entire list/array
newProduct.setCOGS([[...productCOGS]]);

// OR push one COG at a time (automatic generation of ID and auto computation of subtotal)

newProduct.addCOGS(
  "Metal Parts",
  MajikMoney.fromMajor(parseFloat(2000), "PHP"),
  5,
  "G"
);

// Generate Capacity Plan

newProduct.generateCapacityPlan(
  !!period ? monthsInPeriod(period.startMonth, period.endMonth) : 24, // Number of months to generate from the start date.
  capacityPerMonth, // Base units for the first month.
  capacityGrowthRate // Optional growth rate per month (e.g. 0.03 = +3%).
);

// OR Manually  Set Capacity Plan

const productCapacityPlan = [
  {
    month: "2025-12",
    capacity: 8,
  },
  {
    month: "2026-01",
    capacity: 9,
  },
  {
    month: "2026-02",
    capacity: 9,
  },
  {
    month: "2026-03",
    capacity: 10,
  },
  {
    month: "2026-04",
    capacity: 10,
  },
  {
    month: "2026-05",
    capacity: 11,
  },
  {
    month: "2026-06",
    capacity: 12,
  },
  {
    month: "2026-07",
    capacity: 13,
  },
  {
    month: "2026-08",
    capacity: 14,
  },
  {
    month: "2026-09",
    capacity: 15,
  },
  {
    month: "2026-10",
    capacity: 16,
  },
  {
    month: "2026-11",
    capacity: 17,
  },
  {
    month: "2026-12",
    capacity: 18,
  },
  {
    month: "2027-01",
    capacity: 19,
  },
  {
    month: "2027-02",
    capacity: 21,
  },
  {
    month: "2027-03",
    capacity: 22,
  },
  {
    month: "2027-04",
    capacity: 24,
  },
  {
    month: "2027-05",
    capacity: 25,
  },
  {
    month: "2027-06",
    capacity: 27,
  },
  {
    month: "2027-07",
    capacity: 29,
  },
  {
    month: "2027-08",
    capacity: 31,
  },
  {
    month: "2027-09",
    capacity: 33,
  },
  {
    month: "2027-10",
    capacity: 35,
  },
  {
    month: "2027-11",
    capacity: 38,
  },
];

newProduct.setCapacity([...productCapacityPlan]);

// Dynamically change the price if needed
newProduct.setSRP(MajikMoney.fromMajor(12500, "PHP"));

// Dynamically change the name if needed
newProduct.setName("Brand XYZ Phone");

//Dynamically set a description either both in text/html or either (TipTap compatible)
newProduct.setDescription(
  `<p>This is a new product</p>`, // HTML Content
  "This is a new product" // Plain Text
);

newProduct.setDescriptionText(
  "This is a new product" // Plain Text
);

newProduct.setDescriptionHTML(
  `<p>This is a new product</p>` // HTML Content
);

// When done, validate before finalizing and passing to revenue stream

newProduct.validateSelf(true); // Set to true to throw an error, Defaults to false - returns a plain boolean for custom conditions

// Use addRevenue from the MajikRunway class to add this new product
runway.addRevenue(newProduct);

The new MajikProduct will now be added as a new item in RevenueStream and return an updated MajikRunway instance.

Add/Manage Services to Revenue Stream

import { MajikService } from "@thezelijah/majik-service";
import { MajikRunway } from "@thezelijah/majik-runway";
import { MajikMoney } from "@thezelijah/majik-money";
import { BusinessModelType, VATMode } from "@thezelijah/majik-runway/enums";
import { RevenueStream } from "@thezelijah/majik-runway/revenue";
import { ExpenseBreakdown } from "@thezelijah/majik-runway/expenses";
import {
  monthsInPeriod,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";
// Create a new Service Instance

const newService = MajikService.initialize(
  "Home Cleaning",
  ServiceType.TIME_BASED,
  {
    amount: MajikMoney.fromMajor(250, "PHP"),
    unit: RateUnit.PER_HOUR
  }
);

// Prepare and create COS manually - make sure unitCost and subtotal are MajikMoney class instances (Use MajikMoney.fromMajor())
const serviceCOS = [
    {
        "id": "mjkscost-7SgAoOq2",
        "item": "Labor",
        "quantity": 1,
        "unitCost": {
            "__type": "MajikMoney",
            "amount": "8000",
            "currency": "PHP"
        },
        "unit": "PERSON",
        "subtotal": {
            "__type": "MajikMoney",
            "amount": "8000",
            "currency": "PHP"
        }
    },
    {
        "id": "mjkscost-RZPXh7pP",
        "item": "Cleaning Solutions",
        "quantity": 1,
        "unitCost": {
            "__type": "MajikMoney",
            "amount": "3500",
            "currency": "PHP"
        },
        "unit": "PACKAGE",
        "subtotal": {
            "__type": "MajikMoney",
            "amount": "3500",
            "currency": "PHP"
        }
    }
];

// Set Cost of Service and replace the entire list/array
newService.setCOS([...serviceCOS]);

// OR push one COS at a time (automatic generation of ID and auto computation of subtotal)

newService.addCOS(
  "Labor",
  MajikMoney.fromMajor(parseFloat(8000), "PHP"),
  1,
  "PERSON"
);

// Generate Capacity Plan

newService.generateCapacityPlan(
  !!period ? monthsInPeriod(period.startMonth, period.endMonth) : 24, // Number of months to generate from the start date.
  capacityPerMonth, // Base units for the first month.
  capacityGrowthRate // Optional growth rate per month (e.g. 0.03 = +3%).
);

// OR Manually  Set Capacity Plan

const serviceCapacityPlan = [
    {
        "month": "2025-12",
        "capacity": 15
    },
    {
        "month": "2026-01",
        "capacity": 16
    },
    {
        "month": "2026-02",
        "capacity": 18
    },
    {
        "month": "2026-03",
        "capacity": 19
    },
    {
        "month": "2026-04",
        "capacity": 21
    },
    {
        "month": "2026-05",
        "capacity": 23
    },
    {
        "month": "2026-06",
        "capacity": 25
    },
    {
        "month": "2026-07",
        "capacity": 27
    },
    {
        "month": "2026-08",
        "capacity": 30
    },
    {
        "month": "2026-09",
        "capacity": 33
    },
    {
        "month": "2026-10",
        "capacity": 36
    },
    {
        "month": "2026-11",
        "capacity": 39
    },
    {
        "month": "2026-12",
        "capacity": 42
    },
    {
        "month": "2027-01",
        "capacity": 46
    },
    {
        "month": "2027-02",
        "capacity": 50
    },
    {
        "month": "2027-03",
        "capacity": 55
    },
    {
        "month": "2027-04",
        "capacity": 60
    },
    {
        "month": "2027-05",
        "capacity": 65
    },
    {
        "month": "2027-06",
        "capacity": 71
    },
    {
        "month": "2027-07",
        "capacity": 77
    },
    {
        "month": "2027-08",
        "capacity": 84
    },
    {
        "month": "2027-09",
        "capacity": 92
    },
    {
        "month": "2027-10",
        "capacity": 100
    },
    {
        "month": "2027-11",
        "capacity": 109
    }
];

newService.setCapacity([...serviceCapacityPlan]);

// Dynamically change the rate price if needed
newService.setRateAmount(400); // Raw number will automatically be parsed into MajikMoney

// Dynamically change the rate unit if needed
/*
  PER_HOUR = "Per Hour",
  PER_DAY = "Per Day",
  PER_SESSION = "Per Session",
  FIXED = "Per Fixed",
  PER_UNIT = "Per Unit", // e.g., usage-based
*/
newService.setRateUnit(RateUnit.PER_HOUR);

// Dynamically change the name if needed
newService.setName("General Cleaning");

//Dynamically set a description either both in text/html or either (TipTap compatible)
newService.setDescription(
            `<p>This is a new service</p>`, // HTML Content
            "This is a new service" // Plain Text
          );

newService.setDescriptionText(
            "This is a new service" // Plain Text
          );

newService.setDescriptionHTML(
             `<p>This is a new service</p>` // HTML Content
          );

// When done, validate before finalizing and passing to revenue stream

newService.validateSelf(true); // Set to true to throw an error, Defaults to false - returns a plain boolean for custom conditions


// Use addRevenue from the MajikRunway class to add this new service
runway.addRevenue(newService);

The new MajikService will now be added as a new item in RevenueStream and return an updated MajikRunway instance.

Add/Manage Subscriptions to Revenue Stream

import { MajikSubscription } from "@thezelijah/majik-subscription";
import { MajikRunway } from "@thezelijah/majik-runway";
import { MajikMoney } from "@thezelijah/majik-money";
import { BusinessModelType, VATMode } from "@thezelijah/majik-runway/enums";
import { RevenueStream } from "@thezelijah/majik-runway/revenue";
import { ExpenseBreakdown } from "@thezelijah/majik-runway/expenses";
import {
  monthsInPeriod,
  offsetMonthsToYYYYMM,
} from "@thezelijah/majik-runway/utils";
// Create a new Subscription Instance

const newSubscription = MajikSubscription.initialize(
  "Majik Distro Pro",
  SubscriptionType.RECURRING,
  {
    amount: MajikMoney.fromMajor(999,"PHP"),
    unit: RateUnit.PER_USER,
    billingCycle: BillingCycle.MONTHLY,
  }
);

// Prepare and create COS manually - make sure unitCost and subtotal are MajikMoney class instances (Use MajikMoney.fromMajor())
const subscriptionCOS = [
    {
        "id": "mjksubcost-Co7mq4hj",
        "item": "Cloud Services",
        "quantity": 1,
        "unitCost": {
            "__type": "MajikMoney",
            "amount": "2500",
            "currency": "PHP"
        },
        "unit": "MONTH",
        "subtotal": {
            "__type": "MajikMoney",
            "amount": "2500",
            "currency": "PHP"
        }
    },
    {
        "id": "mjksubcost-fm1bQNGp",
        "item": "Customer Support",
        "quantity": 1,
        "unitCost": {
            "__type": "MajikMoney",
            "amount": "2000",
            "currency": "PHP"
        },
        "unit": "MONTH",
        "subtotal": {
            "__type": "MajikMoney",
            "amount": "2000",
            "currency": "PHP"
        }
    }
];

// Set Cost of Service and replace the entire list/array
newSubscription.setCOS([...subscriptionCOS]);

// OR push one COS at a time (automatic generation of ID and auto computation of subtotal)

newSubscription.addCOS(
  "Cloud Services",
  MajikMoney.fromMajor(parseFloat(25), "PHP"),
  1,
  "MONTH"
);

// Generate Capacity Plan

newSubscription.generateCapacityPlan(
  !!period ? monthsInPeriod(period.startMonth, period.endMonth) : 24, // Number of months to generate from the start date.
  capacityPerMonth, // Base units for the first month.
  capacityGrowthRate // Optional growth rate per month (e.g. 0.03 = +3%).
);

// OR Manually  Set Capacity Plan

const subscriptionCapacityPlan = [
    {
        "month": "2025-12",
        "capacity": 16
    },
    {
        "month": "2026-01",
        "capacity": 16
    },
    {
        "month": "2026-02",
        "capacity": 17
    },
    {
        "month": "2026-03",
        "capacity": 17
    },
    {
        "month": "2026-04",
        "capacity": 18
    },
    {
        "month": "2026-05",
        "capacity": 19
    },
    {
        "month": "2026-06",
        "capacity": 19
    },
    {
        "month": "2026-07",
        "capacity": 20
    },
    {
        "month": "2026-08",
        "capacity": 20
    },
    {
        "month": "2026-09",
        "capacity": 21
    },
    {
        "month": "2026-10",
        "capacity": 22
    },
    {
        "month": "2026-11",
        "capacity": 22
    },
    {
        "month": "2026-12",
        "capacity": 23
    },
    {
        "month": "2027-01",
        "capacity": 23
    },
    {
        "month": "2027-02",
        "capacity": 24
    },
    {
        "month": "2027-03",
        "capacity": 25
    },
    {
        "month": "2027-04",
        "capacity": 26
    },
    {
        "month": "2027-05",
        "capacity": 26
    },
    {
        "month": "2027-06",
        "capacity": 27
    },
    {
        "month": "2027-07",
        "capacity": 28
    },
    {
        "month": "2027-08",
        "capacity": 29
    },
    {
        "month": "2027-09",
        "capacity": 30
    },
    {
        "month": "2027-10",
        "capacity": 31
    },
    {
        "month": "2027-11",
        "capacity": 32
    }
];

newSubscription.setCapacity([...subscriptionCapacityPlan]);

// Dynamically change the rate price if needed
newSubscription.setRateAmount(400); // Raw number will automatically be parsed into MajikMoney

// Dynamically change the rate unit if needed
/*
  PER_SUBSCRIBER = "Per Subscriber",
  PER_ACCOUNT = "Per Account",
  PER_USER = "Per User",
  PER_MONTH = "Per Month"
*/
newSubscription.setRateUnit(RateUnit.PER_MONTH);

// Dynamically change the billing cycle if needed
/*
   DAILY = "Daily",
  WEEKLY = "Weekly",
  MONTHLY = "Monthly",
  QUARTERLY = "Quarterly",
  YEARLY = "Yearly"
*/
newSubscription.setBillingCycle(BillingCycle.MONTHLY);

// Dynamically change the name if needed
newSubscription.setName("Majik Distro Advanced Plus");

//Dynamically set a description either both in text/html or either (TipTap compatible)
newSubscription.setDescription(
            `<p>This is a new subscription</p>`, // HTML Content
            "This is a new subscription" // Plain Text
          );

newSubscription.setDescriptionText(
            "This is a new subscription" // Plain Text
          );

newSubscription.setDescriptionHTML(
             `<p>This is a new subscription</p>` // HTML Content
          );

// When done, validate before finalizing and passing to revenue stream

newSubscription.validateSelf(true); // Set to true to throw an error, Defaults to false - returns a plain boolean for custom conditions


// Use addRevenue from the MajikRunway class to add this new subscription
runway.addRevenue(newSubscription);

The new MajikSubscription will now be added as a new item in RevenueStream and return an updated MajikRunway instance.

Update Existing Revenue Item

// To edit/update an item

const updatedRevisedProduct = newProduct.setName("Hello Phone");

// This method accepts MajikProduct, MajikService or MajikSubscription

runway.updateRevenueItem(
  updatedRevisedProduct.id, // The ID of the product you want to update
  updatedRevisedProduct // The updated product
);

The updated MajikProduct/MajikService/MaikSubscription will now be reflected in the current RevenueStream and return an updated MajikRunway instance.

Remove an Existing Revenue Item

// To remove/delete an item

runway.removeRevenueByID(
  updatedItem.id // The ID of the product you want to remove
);

The removed MajikProduct/MajikService/MaikSubscription will now be reflected in the current RevenueStream and return an updated MajikRunway instance.


Expenses and Operating Costs

Expenses are grouped under an ExpenseBreakdown.

Expense instances are immutable and return a new instance when updated.

Expenses directly affect:

  • Monthly burn
  • Cash balance
  • Runway length

Expense types may include:

  • Fixed
  • Variable
  • One-time

Add/Manage Expenses

There are 3 main types of expenses you can create:

  • One Time
  • Recurring
  • Capital
Create Recurring Expense

Recurring expenses represent predictable, repeating cash outflows that occur every month within an active period.

Examples include:

  • Salaries and contractor payments
  • Rent and utilities
  • Software subscriptions
  • Baseline infrastructure costs

Characteristics:

  • Applied monthly between a defined start and end period
  • Reduce cash balance every month they are active
  • Contribute directly to monthly burn
  • Commonly classified as Operating or Variable

Recurring expenses form the structural baseline of a company’s burn rate.

import { Expense } from "@thezelijah/majik-runway/expenses/expense";
import { Recurrence } from "./enums";


const newExpense = Expense.recurring(
  undefined, // Optional ID. If undefined, automatically generates one
  "Office Rent",
  15000, // Automatically parses into MajikMoney
  "PHP",
  Recurrence.Monthly,
  period, // use  the same Period you have from your MajikRunway instance
  true, // Set to false if not Tax Deductible (defaults to false)
);


// Use addExpense from the MajikRunway class to add this new expense item
runway.addExpense(newExpense)

// To quickly create and add directly use this method (No need to input period, uses the runway's period)
runway.addRecurringExpense(
  "Office Rent",
  15000, // Automatically parses into MajikMoney
  "PHP",
  Recurrence.Monthly,
  true, // Set to false if not Tax Deductible (defaults to false)
  undefined  // Optional ID. If undefined, automatically generates one
)

The new Expense will now be added as a new item in ExpenseBreakdown and return an updated MajikRunway instance.

Create One Time Expense

One-time expenses represent single, non-recurring cash outflows that occur in a specific month.

Examples include:

  • Legal and incorporation fees
  • Marketing launches or campaigns
  • One-off consulting or audit work
  • Setup or migration costs

Characteristics:

  • Occur once at a specific YYYYMM
  • Reduce cash in the month they occur
  • Do not persist or repeat
  • May be classified as Operating or Variable

One-time expenses often cause short-term spikes in burn but do not affect long-term baseline costs.

import { Expense } from "@thezelijah/majik-runway/expenses/expense";

  
const newExpense = Expense.oneTime(
  undefined,  // Optional ID. If undefined, automatically generates one
  "Event PubMats",
  35000, // Automatically parses into MajikMoney
  "PHP",
  "2026-10", // Date in YYYY-MM string format
  true, // Set to false if not Tax Deductible (defaults to false)
);


// Use addExpense from the MajikRunway class to add this new expense item
runway.addExpense(newExpense)

// To quickly create and add directly use this method 
runway.addOneTimeExpense(
  "Event PubMats",
  35000, // Automatically parses into MajikMoney
  "PHP",
  "2026-10", // Date in YYYY-MM string format
  true, // Set to false if not Tax Deductible (defaults to false)
  undefined  // Optional ID. If undefined, automatically generates one
)

The new Expense will now be added as a new item in ExpenseBreakdown and return an updated MajikRunway instance.

Create Capital Expense

Capital expenses represent large, strategic cash outflows intended to support long-term operations or growth.

Examples include:

  • Equipment and hardware purchases
  • Initial product or platform build costs
  • Long-term tooling or infrastructure investments

Characteristics:

  • Typically one-time and high-value
  • Classified explicitly as ExpenseType.Capital
  • Reduce cash immediately in the month they occur
  • Are not depreciated or amortized

In Majik Runway, capital expenses are treated as direct cash outflows to preserve cash-flow realism.

import { Expense } from "@thezelijah/majik-runway/expenses/expense";

  
const newExpense = Expense.capital(
  undefined,  // Optional ID. If undefined, automatically generates one
  "Photocopy Machine",
  35000, // Automatically parses into MajikMoney
  "PHP",
  "2026-10", // Date in YYYY-MM string format
  36, // Number of months to depreciate
  undefined, // Optional residual value after depreciation
  true, // Set to false if not Tax Deductible (defaults to false)
);
        
// Use addExpense from the MajikRunway class to add this new expense item
runway.addExpense(newExpense)

// To quickly create and add directly use this method 
runway.addCapitalExpense(
  "Photocopy Machine",
  35000, // Automatically parses into MajikMoney
  36, // Number of months to depreciate
  "PHP",
  "2026-10", // Date in YYYY-MM string format
  undefined, // Optional residual value after depreciation
  true, // Set to false if not Tax Deductible (defaults to false)
  undefined  // Optional ID. If undefined, automatically generates one
)

The new expense will now be added as a new item in expense breakdown and return an updated MajikRunway instance.

Update an Existing Expense Item

// To update an existing item

const updatedRevisedExpense = newExpense
.rename("Hello Phone") // Renames the expense
.withAmount(MajikMoney.fromMajor(500),"PHP"); // Updates the amount

// Important: Since expense instances are immutable, all update methods return a new instance 

// This method accepts Expense

runway.updateExpenseItem(
  updatedRevisedExpense.id, // The ID of the expense you want to update
  updatedRevisedExpense // The updated expense instance
);

The updated expense will now be reflected in the current expense breakdown and return an updated MajikRunway instance.

Remove an Existing Expense Item

// To remove/delete an item

runway.removeExpenseByID(
  updatedItem.id // The ID of the expense you want to remove
);

The removed expense will now be reflected in the current expense breakdown and return an updated MajikRunway instance.


Funding

Funding events are managed under the FundingManager.

Funding instances are immutable and return a new instance when updated.

Funding directly affects:

  • Cash balance
  • Runway length
  • Liquidity over time

Funding does not directly affect:

  • Revenue
  • Profit or margins
  • Operating burn

Each funding event:

  • Occurs at a specific YYYYMM
  • Injects cash into the model
  • Is treated as a non-operational cash inflow
  • Has no implicit dilution, repayment, or interest unless explicitly modeled

Majik Runway treats funding as capital flow, fully decoupled from operational performance.


Add/Manage Funding Events

There are 3 main types of funding events you can create:

  • Equity
  • Debt
  • Grant
Create Equity Funding Event

Equity funding represents capital raised in exchange for ownership in the company.

Examples include:

  • Angel investments
  • Venture capital rounds
  • Founder capital contributions

Characteristics:

  • Increases cash balance in the month received
  • Does not count as revenue
  • Does not affect profit or margins
  • Has no implicit dilution modeling

In Majik Runway, equity funding is treated purely as a cash injection, separate from ownership or valuation mechanics.

import { FundingEvent } from "@thezelijah/majik-runway/funding/funding";


const newFundingEvent = FundingEvent.equity(
  "Round 1 Funding",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  "PHP",
  undefined // Optional ID. If undefined, automatically generates one
);



// Use addFundingEvent from the MajikRunway class to add this new funding event
runway.addFundingEvent(newFundingEvent)

// To quickly create and add directly use this method 
runway.addEquity(
  "Round 1 Funding",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  undefined // Optional ID. If undefined, automatically generates one
)

The new FundingEvent will now be added as a new item inside FundingManager and return an updated MajikRunway instance.

Create Debt Funding Event
import { FundingEvent } from "@thezelijah/majik-runway/funding/funding";


const newFundingEvent = FundingEvent.debt(
  "BPI Loan",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  new Date().toISOString(), // ISO date string representing when the debt must be fully repaid
  "PHP",
  0.05, // Annual interest rate as a decimal ratio (default: 0, e.g., 0.05 for 5%)
  0, // Upfront payment at the start of the loan (default: 0)
  undefined // Optional ID. If undefined, automatically generates one
);



// Use addFundingEvent from the MajikRunway class to add this new funding event
runway.addFundingEvent(newFundingEvent)

// To quickly create and add directly use this method 
runway.addDebt(
 "BPI Loan",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  new Date().toISOString(), // ISO date string representing when the debt must be fully repaid
  "PHP",
  0.05, // Annual interest rate as a decimal ratio (default: 0, e.g., 0.05 for 5%)
  0, // Upfront payment at the start of the loan (default: 0)
  undefined // Optional ID. If undefined, automatically generates one
)

The new FundingEvent will now be added as a new item inside FundingManager and return an updated MajikRunway instance.

Create Grant Funding Event
import { FundingEvent } from "@thezelijah/majik-runway/funding/funding";


const newFundingEvent = FundingEvent.grant(
  "Round 1 Funding",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  "PHP",
  undefined // Optional ID. If undefined, automatically generates one
);



// Use addFundingEvent from the MajikRunway class to add this new funding event
runway.addFundingEvent(newFundingEvent)

// To quickly create and add directly use this method 
runway.addGrant(
  "Round 1 Funding",
  300000, // Automatically parses into MajikMoney
  "2026-10", // Date in YYYY-MM string format
  undefined // Optional ID. If undefined, automatically generates one
)

The new FundingEvent will now be added as a new item inside FundingManager and return an updated MajikRunway instance.

Update an Existing Funding Event

// To update an existing item

const updatedRevisedFunding= newFundingEvent
.rename("HSBC Loan") // Renames the funding event
.reschedule("2028-03") // Reschedules the funding to another date
.withAmount(MajikMoney.fromMajor(250000),"PHP"); // Updates the amount

// Important: Since fudning event instances are immutable, all update methods return a new instance 

// This method accepts FundingEvent

runway.updateFundingEvent(
  updatedRevisedFunding.id, // The ID of the expense you want to update
  updatedRevisedFunding // The updated expense instance
);

The updated FundingEvent will now be reflected in the current FundingManager and return an updated MajikRunway instance.

Remove an Existing Funding Event

// To remove/delete an item

runway.removeFundingByID(
  updatedItem.id // The ID of the funding event you want to remove
);

The removed FundingEvent will now be reflected in the current FundingManager and return an updated MajikRunway instance.


Tax Configuration

Majik Runway supports VAT and Non-VAT systems with income tax modeling.

import { TaxConfig } from "@thezelijah/majik-runway/types/tax";

// To set/update your MajikRunway instance's tax config pass the new taxconfig object to the updateModel

const newTaxConfig: TaxConfig = {
  vatMode: VATMode.VAT,
  vatRate: 0.12,             // VAT businesses
  percentageTaxRate: 0.03,   // Non-VAT businesses
  incomeTaxRate: 0.08,
}

runway.updateTaxConfig(newTaxConfig); 

Rules:

  • VAT & Percentage Tax are mutually exclusive
  • Taxes are applied automatically to projections
  • Net cashflow reflects tax impact

Initial Cash & Currency

Initial cash defines your starting runway position (opening balance).


// Construct the initial cash using MajikMoney
const initialCash = MajikMoney.fromMajor(15000,"PHP");


// Include the initial cash upon setup/initialization of your MajikRunway instance
const runwayInstance = MajikRunway.initialize({
// .. insert other parameters
  money: initialCash,
});


// To set/update your MajikRunway instance's initial cash pass the new MajikMoney to the updateModel() method of MajikRunway

runway.updateModel({
  money: MajikMoney.fromMajor(25000,"PHP"),
}); 

// Or to quickly update it directly

runway.setInitialCash(MajikMoney.fromMajor(25000,"PHP"));

Rules:

  • All internal computations use MajikMoney
  • Currency consistency is enforced
  • Avoid raw numbers

Updating the Model

Majik Runway is designed to be mutable.

You can update:

  • Business model
  • Period
  • Tax assumptions
  • Revenue streams (Products, Services, and Subscription Plans)
  • Expenses
  • Initial Cash (Opening Balance)

All changes recompute projections instantly.

interface BusinessModel {
  money: MajikMoney;
  expenses: ExpenseBreakdown;
  revenues: RevenueStream;
  taxConfig: TaxConfig;
  funding: FundingManager;
  type?: BusinessModelType;
  period: PeriodYYYYMM;
  id?: string;
}

runway.updateModel({
 // pass in partial properties
}); 


Mental Model

[ Assumptions ]
      ↓
[ Revenue Streams ] + [ Expenses ] + [ Funding ] + [ Taxes ]
      ↓
[ Monthly Cashflow Engine ]
      ↓
[ Burn Rate & Runway Projection ]
  • MajikRunway is the central financial engine.
  • Revenue, Expense, Funding, Tax are pluggable sub-managers.
  • Dashboard Snapshot is your single source of truth for rendering.
  • Updating any sub-manager triggers a refresh / recompute.
  • Charts consume methods from each sub-manager for time-series or trend plotting.
  • Dynamic coloring thresholds provide visual alerts for financial health.

Why This Design Works

  • Finance-correct but intuitive
  • No spreadsheet drift
  • Reactive & composable
  • UI-agnostic
  • Investor-ready projections

Example Usage

import {
  SubscriptionType,
  RateUnit,
  BillingCycle,
  CapacityPeriodResizeMode,
} from "@thezelijah/majik-subscription/enums";
import { MajikMoney } from "@thezelijah/majik-money";


const proPlan = MajikSubscription.initialize(
  "Pro Plan",
  SubscriptionType.RECURRING,
  {
    amount: MajikMoney.fromMajor(499, "PHP"),
    unit: RateUnit.PER_USER,
    billingCycle: BillingCycle.MONTHLY,
  },
  "Advanced SaaS plan",
  "PRO-PLAN-001"
)
  .setDescriptionHTML("<p>Best plan for growing teams.</p>")
  .setDescriptionSEO("Pro SaaS subscription plan")
  .addCOS("Cloud Hosting", MajikMoney.fromMajor(300, "PHP"), 1, "per user")
  .addCOS("Customer Support", MajikMoney.fromMajor(100, "PHP"), 1, "per user")
  .generateCapacityPlan(12, 500) // 12 months, 500 subscribers
  .recomputeCapacityPeriod(
    "2025-01",
    "2025-12",
    CapacityPeriodResizeMode.DISTRIBUTE
  );

// Capacity insights
console.log("Total Capacity:", proPlan.totalCapacity);

// Monthly finance
const month = "2025-06";
console.log(`${month} Revenue:`, proPlan.getRevenue(month).value.toFormat());
console.log(`${month} COS:`, proPlan.getCOS(month).value.toFormat());
console.log(`${month} Profit:`, proPlan.getProfit(month).value.toFormat());
console.log(`${month} Margin:`, proPlan.getMargin(month).toFixed(2) + "%");

// Serialization
const json = proPlan.toJSON();
const restored = MajikSubscription.parseFromJSON(json);

console.log("Restored Subscription:", restored.metadata.description.text);

Dashboard Integration & Real-Time Financial Modeling

This section demonstrates how Majik Runway is designed to be used inside a production-grade dashboard. Instead of treating financial logic as isolated calculations, Majik Runway acts as a single source of truth that powers KPIs, charts, health indicators, and editable financial modules in real time.

The example below shows how to wire MajikRunway into a React / Next.js dashboard, enabling:

  • Live runway and burn calculations
  • Revenue, expense, funding, and tax modeling
  • Health indicators and financial risk signals
  • Interactive charts and managers
  • Immutable updates with snapshot-based rendering
"use client";

import React, { useMemo, useState } from "react";
import styled from "styled-components";
import {
  ChartLineIcon,
  CurrencyDollarSimpleIcon,
  GaugeIcon,
  CoinsIcon,
  GearIcon,
  BankIcon,
  InfoIcon,
  TrendUpIcon,
  FireIcon,
  ScalesIcon,
  CalendarXIcon,
} from "@phosphor-icons/react";
import DynamicPagedTab, {
  TabContent,
} from "@/components/functional/DynamicPagedTab";
import { MajikRunway } from "@/SDK/tools/business/majik-runway/majik-runway";

import ChartRevenueTrend from "./Charts/ChartRevenueTrend";
import ChartExpensePie from "./Charts/ChartExpensePie";
import ChartCashflowBar from "./Charts/ChartCashflowBar";
import RevenueStreamManager from "./RevenueStreams/RevenueStreamManager";
import ExpenseManager from "./Expenses/ExpenseManager";
import { RevenueStream } from "@/SDK/tools/business/majik-runway/revenue";
import { ExpenseBreakdown } from "@/SDK/tools/business/majik-runway/expenses/expense-breakdown";
import { toast } from "sonner";
import RunwayTaxConfig from "./RunwayTaxConfig";
import { TaxConfig } from "@/SDK/tools/business/majik-runway/types/tax";
import { formatPercentage } from "@/utils/helper";
import moment from "moment";
import { yyyyMMToDate } from "@/SDK/tools/business/majik-runway/utils";
import MajikRunwayHealthIndicator from "./MajikRunwayHealthIndicator";
import { DynamicColoredValue } from "@/components/foundations/DynamicColoredValue";
import theme from "@/globals/theme";
import FundingEventsManager from "./Funding/FundingEventsManager";
import { FundingManager } from "@/SDK/tools/business/majik-runway/funding/funding-manager";
import ChartFundingTimeSeries from "./Charts/ChartFundingTimeSeries";
import ChartExpenseTrend from "./Charts/ChartExpenseTrend";
import { TargetIcon } from "lucide-react";
import ChartGrossMarginTrend from "./Charts/ChartGrossMarginTrend";

// ======== Styled Components ========
const RootContainer = styled.div`
// css
`;

const HeaderCards = styled.div`
// css
`;

const TopHeaderRow = styled.div`
// css
`;

const Card = styled.div`
// css
`;

const CardTitle = styled.div`
// css
`;

const CardValue = styled.div`
// css
`;

const CardSubtext = styled.div`
// css
`;

const MainGrid = styled.div`
// css
`;

const ChartPlaceholder = styled.div`
// css
`;

interface DashboardMajikRunwayProps {
  runway: MajikRunway;
  onUpdate?: (newRunway: MajikRunway) => void;
}

// ======== Main Component ========

const DashboardMajikRunway: React.FC<DashboardMajikRunwayProps> = ({
  runway,
  onUpdate,
}) => {
  const [refreshKey, setRefreshKey] = useState<number>(0);
  const dashboardSnapshot = useMemo(
    () => runway.getDashboardSnapshot(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const revenueStream = useMemo(
    () => runway.revenueStream(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const expense = useMemo(
    () => runway.expenseBreakdown(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const fundingManager = useMemo(
    () => runway.funding(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const cashflow = useMemo(
    () => runway.getCashflowPlotlyBar(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const taxConfig = useMemo(
    () => runway.taxConfig(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [runway, refreshKey]
  );

  const handleUpdateRevenueStream = (input: RevenueStream) => {
    try {
      console.log("Revenue Stream: ", input);
      const updatedRunway = runway.updateModel({
        revenues: input,
      });

      setRefreshKey((prev) => prev + 1);
      onUpdate?.(updatedRunway);
    } catch (error) {
      console.error("Problem while updating Revenue Stream: ", error);
      toast.error("Error", {
        description: `Oops! There seems to be a problem while updating: ${error}`,
        id: "error-input-majik-runway-dashboard-revenue",
      });
    }
  };

  const handleUpdateExpenseBreakdown = (input: ExpenseBreakdown) => {
    try {
      console.log("Expense Breakdown: ", input);
      const updatedRunway = runway.updateModel({
        expenses: input,
      });
      setRefreshKey((prev) => prev + 1);
      onUpdate?.(updatedRunway);
    } catch (error) {
      console.error("Problem while updating Expense Breakdown: ", error);
      toast.error("Error", {
        description: `Oops! There seems to be a problem while updating: ${error}`,
        id: "error-input-majik-runway-dashboard-expense",
      });
    }
  };

  const handleUpdateFundingManager = (input: FundingManager) => {
    try {
      console.log("Funding Manager: ", input);
      const updatedRunway = runway.updateModel({
        funding: input,
      });
      setRefreshKey((prev) => prev + 1);
      onUpdate?.(updatedRunway);
    } catch (error) {
      console.error("Problem while updating Funding Manager: ", error);
      toast.error("Error", {
        description: `Oops! There seems to be a problem while updating: ${error}`,
        id: "error-input-majik-runway-dashboard-funding",
      });
    }
  };

  const handleUpdateTaxConfig = (input: TaxConfig) => {
    try {
      console.log("Tax Config: ", input);
      const updatedRunway = runway.updateModel({
        taxConfig: input,
      });
      setRefreshKey((prev) => prev + 1);
      onUpdate?.(updatedRunway);
    } catch (error) {
      console.error("Problem while updating Tax Config: ", error);
      toast.error("Error", {
        description: `Oops! There seems to be a problem while updating: ${error}`,
        id: "error-input-majik-runway-dashboard-tax",
      });
    }
  };

  const InformationTabs: TabContent[] = [
    {
      id: "info-overview",
      name: "Overview",
      icon: InfoIcon,
      content: (
        <>
          <TopHeaderRow>
            <MajikRunwayHealthIndicator
              health={dashboardSnapshot.runwayHealth}
            />
          </TopHeaderRow>

          {/* ===== Header / KPI Cards ===== */}
          <HeaderCards>
            <Card>
              <CardTitle>
                <CoinsIcon size={20} /> Runway Remaining
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.runwayMonths} // numeric months remaining
                  colorsMap={[
                    { color: theme.colors.error, max: 2.99 }, // red if less than 3 months
                    { color: theme.colors.brand.white, min: 3, max: 6 }, // yellow if 3–6 months
                    { color: theme.colors.brand.green, min: 6.01 }, // green if above 6 months
                  ]}
                  size={28}
                  weight={700}
                >
                  {dashboardSnapshot.runwayMonths} months
                </DynamicColoredValue>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <CurrencyDollarSimpleIcon size={20} /> Opening Balance
              </CardTitle>
              <CardValue>{dashboardSnapshot.cashOnHand.format()}</CardValue>
            </Card>
            <Card>
              <CardTitle>
                <BankIcon size={20} /> Total Funding
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.funding.totalFunding.format()}
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <GaugeIcon size={20} /> Net Monthly Burn
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.avgNetBurn.toMajor()}
                  colorsMap={[
                    { color: theme.colors.brand.green, max: -1 }, // green if negative
                    { color: theme.colors.brand.white, min: 0, max: 0 }, // yellow if zero
                    { color: theme.colors.error, min: 1 }, // red if positive
                  ]}
                  size={28}
                  weight={700}
                >
                  {dashboardSnapshot.avgNetBurn.format()}
                </DynamicColoredValue>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <ChartLineIcon size={20} /> Projected Revenue Next Month
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.nextMonthRevenue.format()}
              </CardValue>
            </Card>
          </HeaderCards>
          {/* ===== SubHeader / Secondary KPI Cards ===== */}
          <HeaderCards>
            <Card>
              <CardTitle>
                <TargetIcon size={20} />
                Break-Even Month
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.breakEvenMonth ?? "Not Available"}
                <CardSubtext>
                  {!!dashboardSnapshot.breakEvenMonth
                    ? moment(
                        yyyyMMToDate(dashboardSnapshot.breakEvenMonth)
                      ).fromNow()
                    : null}
                </CardSubtext>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <TrendUpIcon size={20} />
                Revenue Growth Rate (MoM)
              </CardTitle>
              <CardValue>
                {formatPercentage(
                  dashboardSnapshot.revenueGrowthRateCMGR ?? 0,
                  true
                )}
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <FireIcon size={20} />
                Burn Efficiency
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.burnEfficiency ?? 0}
                  colorsMap={[
                    { color: theme.colors.error, max: 1 },
                    { color: theme.colors.brand.white, min: 1.1, max: 6 },
                    { color: theme.colors.brand.green, min: 6.1 },
                  ]}
                  size={28}
                  weight={700}
                >
                  {formatPercentage(
                    dashboardSnapshot.burnEfficiency ?? 0,
                    true
                  )}
                </DynamicColoredValue>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <ScalesIcon size={20} />
                Debt Ratio
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.funding.debtRatio ?? 0}
                  colorsMap={[
                    { color: theme.colors.brand.green, max: 0.3 }, // Healthy (low debt)
                    { color: theme.colors.brand.white, min: 0.31, max: 0.6 }, // Moderate
                    { color: theme.colors.error, min: 0.61 }, // Risky (high debt)
                  ]}
                  size={28}
                  weight={700}
                >
                  {formatPercentage(
                    dashboardSnapshot.funding.debtRatio ?? 0,
                    true
                  )}
                </DynamicColoredValue>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <CalendarXIcon size={20} />
                Cash-Out Date
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.cashOutDate ?? "Not Available"}
              </CardValue>
            </Card>
          </HeaderCards>

          {/* ===== Main Grid / Charts ===== */}
          <MainGrid>
            <ChartPlaceholder>
              <ChartRevenueTrend
                data={revenueStream.toMonthlyRevenueTraces()}
              />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <ChartCashflowBar data={cashflow} />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <ChartExpenseTrend data={expense.getExpenseTrendByCategory()} />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <ChartExpensePie data={expense.expenseBreakdownPlot()} />
            </ChartPlaceholder>

            <ChartPlaceholder>
              <ChartFundingTimeSeries
                data={fundingManager.generateFundingTimeSeries()}
              />
            </ChartPlaceholder>

            <ChartPlaceholder>
              <ChartGrossMarginTrend
                data={revenueStream.toGrossMarginTrendTraces()}
              />
            </ChartPlaceholder>
          </MainGrid>
        </>
      ),
    },
    {
      id: "info-revenue",
      name: "Revenue Streams",
      icon: CurrencyDollarSimpleIcon,
      content: (
        <>
          <RevenueStreamManager
            revenueStream={revenueStream}
            onUpdate={handleUpdateRevenueStream}
          />
        </>
      ),
    },
    {
      id: "info-expenses",
      name: "Expenses",
      icon: GaugeIcon,
      content: (
        <>
          <ExpenseManager
            expenseBreakdown={expense}
            onUpdate={handleUpdateExpenseBreakdown}
          />
        </>
      ),
    },
    {
      id: "info-funding",
      name: "Funding Events",
      icon: BankIcon,
      content: (
        <>
          {" "}
          <FundingEventsManager
            fundingManager={fundingManager}
            onUpdate={handleUpdateFundingManager}
          />
        </>
      ),
    },
    {
      id: "info-tax",
      name: "Tax Configuration",
      icon: GearIcon,
      content: (
        <>
          <RunwayTaxConfig
            formData={taxConfig}
            onSubmit={handleUpdateTaxConfig}
          />
        </>
      ),
    },
  ];

  return (
    <RootContainer>
      {/* ===== Tabs ===== */}

      <DynamicPagedTab tabs={InformationTabs} position="left" />
    </RootContainer>
  );
};

export default DashboardMajikRunway;

Utilities

  • validateSelf(throwError?: boolean) → validates all required fields
  • finalize() → converts to JSON with auto-generated ID
  • toJSON() → serialize with proper MajikMoney handling
  • parseFromJSON(json: string | object) → reconstruct a MajikSubscription instance

Use Cases

What is Majik Runway Used For?

MajikRunway is a financial modeling and runway simulation engine designed to help teams understand, forecast, and control cash flow, burn, growth, and sustainability over time. It is optimized for startups, creative businesses, SaaS products, agencies, and investor-facing financial planning.

Core Use Cases

1. Startup Runway & Burn Management

  • Calculate cash runway in months based on current burn rate
  • Track net burn vs gross burn
  • Identify break-even points
  • Simulate cost increases or revenue growth
  • Evaluate survival scenarios under different funding conditions

***Real-world