How to Use IronPPT with Google Antigravity

Curtis Chau
Curtis Chau
Updated: 19 października 2026

A project management office publishes a status deck for every project each Monday. In StatusDesk, the office's web app, a project manager clicks Download status deck on a project's row and receives a PowerPoint file that the server built a moment earlier. A deck can download without an error and still open with a blank first slide, text boxes stacked on top of each other or another project's slides. None of these faults appears in a build log.

Google Antigravity can check the page the way a project manager uses it. Its /browser command launches a sandboxed browser subagent that clicks each Download button and captures a screenshot. IronPPT builds the deck on the server from text, shapes and images, the elements covered in IronPPT's slide element tutorial. This guide builds the page with both tools and checks it against six projects whose decks are known in advance.

This guide is intended for developers using AI agents to write .NET code that produces PowerPoint files on a web server. It does not address slide design or corporate templates.

Quickstart
NuGetZainstaluj za pomocą NuGet

PM > Install-Package IronPPT

Install IronPPT by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

With the StatusDesk project open in Antigravity, describe the status page.

/plan Give StatusDesk, the PMO's .NET 9 MVC site, a /Status page.
Each project row gets a Download status deck button.
The nav bar gets a Status link.
Install IronPPT 2026.10.1. Its namespace is IronPPT.
App_Data/projects.json holds the projects and the report week.

Each deck has these slides, in order:

1. Title: project name, "Status report, week of" plus the week, owner.
2. Status: status word, status-colored rectangle, summary, logo.
3. Milestones: name, due date and state, at most five per slide.
4. Risks: every risk, only if there are any.

Follow these rules too:

- Status colors: Green 00B050, Amber FFC000, Red C00000.
- Logo: App_Data/logos/<Id>.png, skipped if missing or unreadable.
- Text boxes don't overlap, and nothing runs off the slide IronPPT creates.
- Download name: status-<Id>.pptx, with the PowerPoint content type.
- If a deck fails, the page shows one plain sentence, never exception text.

Startup code reads IRONPPT_LICENSE_KEY itself and sets IronPPT.License.LicenseKey once.
Never print, log or store the key, or list environment variables.

Answer these in the Implementation Plan:

- What does a new PresentationDocument hold, and how big is its slide?
- Where does AddText put a text box, and what unit do Position, Width and Height use?
- How do the deck's bytes get into the HTTP response?

Show me the plan, then build until dotnet build shows zero errors.
Text

A passing build proves none of the three answers, so read them in the plan before approving it.

IronPPT does not read IRONPPT_LICENSE_KEY on its own. In our probe, License.IsLicensed remained false when that variable alone was set, while an environment variable named IronPPT.LicenseKey licensed the process without any code. Assigning License.LicenseKey at startup keeps the variable name under the application's control. The license key guide describes the other ways to apply the key.



What is Google Antigravity, and how is it different from a chat assistant?

According to Google's overview, the Antigravity App is a standalone desktop application for launching, orchestrating and inspecting teams of AI agents. Its agents return Artifacts, such as an Implementation Plan, a Walkthrough and screenshots, where a chat assistant returns text.

An agent that picks a package name without checking it can install malware, because attackers publish packages under names that language models hallucinate. This attack is called slopsquatting. Name the IronPPT package and its version in every task that adds a package. The AI coding assistants guide lists IronPPT's documentation files for agents, and its acceptance test fits in one prompt.

What should you check in the plan, the code changes and the Walkthrough?

In the plan, compare each answer with the deck contents measured below. In the code changes, find where each text box receives its position, and confirm each IronPPT member in the API reference.

The Walkthrough should hold a screenshot of each download. In the desktop app, Antigravity stores each screenshot as an image artifact that accepts comments, so a comment on the wrong row identifies the project without a new prompt.

How do you test the download button in a real browser?

Provide /browser with a table that it can complete only by downloading every deck and opening the file.

/browser Launch StatusDesk with dotnet run as a background process.
Then open http://localhost:5326/Status.

Do this for every project, top to bottom:

1. Click Download status deck.
2. Save the file as artifacts/decks/status-<Id>.pptx.
3. Take a screenshot of the page.

Next, write a console tool in tools/DeckCheck that opens a deck with IronPPT.
It prints the slide count and each slide's text, shape count and image count.
Leave tools/DeckCheck out of the web project's compile items.

Run DeckCheck on each deck in artifacts/decks and compare it with TestData/expected-decks.csv.
Put the results in a Walkthrough table and in artifacts/deckcheck.md.
The table has these columns:

- project
- slides expected and slides found
- first text on slide 1
- images found
- pass or fail

If a row fails, trace the cause in the deck code before you edit.
Then fix it, restart the site and download every deck again.
At the end, kill the dotnet process by its PID and show me the table.
Text

The six projects cover the cases a status deck has to handle. Atlas and Borealis have a logo and open risks, Cobalt has no logo file, and Delta has no risks, so its deck ends after the milestones. The logo for Ember holds an HTML error page from a failed export instead of an image, and Fjord has seven milestones, which need a second milestone slide.

StatusDesk /Status page listing six projects, each with a Download status deck button

The /Status page after the download for P-102, as the final browser pass captured it without its stylesheets.

Output

The Borealis deck below is the file that the final browser pass downloaded, unchanged.

Download status-P-102.pptx

The Borealis Mobile App status deck, four slides with the logo on the second.

What does a new IronPPT deck contain before you add anything?

Every new PresentationDocument starts with one slide. That slide uses the Title Slide layout and keeps two empty placeholders, Title 1 and Subtitle 2, and AddSlide appends after it. The Add Slide guide notes the existing slide.

The slide is 4:3, 720 by 540 points. Position, Width and Height take values in points, so a width of 100 writes 1,270,000 EMU. The comments in the slide element tutorial that call the unit pixels are wrong for IronPPT 2026.10.1.

Slide.AddText creates a text box with no position, as the IronPPT hub article measured. Setting Position, Width and Height on the last item of Slide.TextBoxes places the box that the call added. Font.FontFamily writes no typeface into the file, and Font.LatinFont does.

A calibration build that called AddSlide for every slide and AddText without positions opened all six decks with a blank slide, and 6 to 15 text boxes in each deck shared one position. A second build that reused the existing slide and positioned every text box passed all six projects.

How do you set up a project Antigravity can work in?

Start the agent on a repository that compiles and serves a page. This guide's project was created with dotnet new mvc -f net9.0 and built once before the first prompt. Its http profile in Properties/launchSettings.json serves port 5326.

Which IronPPT version is in StatusDesk.csproj? Quote that PackageReference line.
Next, find and quote the code that sets the key.
Answer from the code without running the app.

Check the key handling against these rules:

- The key comes in through IRONPPT_LICENSE_KEY at startup and nowhere else.
- No appsettings file has an IronPPT.LicenseKey entry.
- The key isn't set in Properties/launchSettings.json either.
- If the variable is missing, startup logs a single warning with its name and no value.
- IronPPT licensing errors get logged by type name only, because their message can contain the key.

If that warning is missing, add it to Program.cs and change nothing else.
For each other rule that fails, give me the file and line.
Text

Without a license key, IronPPT 2026.10.1 built a deck in memory and opened an existing PPTX file. Save threw a LicensingException titled "Production License Required", and no output file appeared. On the calibration page the first unlicensed download failed after 871 ms and later ones in 33 to 48 ms. A handler has to match the LicensingException by type name, because the type is not public.

With an invalid key, the LicensingException message repeated the key text it was given. A calibration download that displayed the exception message returned that key text in the response. A log entry that records the message carries the same text, which is why the rule above logs the type name only.

How do you apply the deck rules to later tasks?

When every row passes, record the session's decisions as rules. According to Antigravity's rules documentation, the CLI reads AGENTS.md, GEMINI.md and .agents/rules from the repository root and from subdirectories. The /learn command condenses corrections from the session into persistent rules.

/learn Save what this session decided in a new AGENTS.md at the repo root.
Put it under the heading "Status decks" and cover these points:

- what a new PresentationDocument holds before any slide is added
- the unit of Position, Width and Height, and the slide size
- how each text box gets a position of its own
- how the deck's bytes reach the HTTP response
- that the key comes only from IRONPPT_LICENSE_KEY and is never printed, logged or stored
- that no command lists the environment variables
- that a change is done once every row of TestData/expected-decks.csv passes in /browser

Show me the file when you're done.
Text

What happens when several people download at once?

IronPPT is not safe to use on two requests at the same time, even when each request builds its own PresentationDocument and saves to its own file. Building decks in memory on 16 threads produced no errors. Saving or loading on parallel threads failed, with errors such as InvalidOperationException and IOException, and some files that saved without an exception held another deck's slides. With a lock around Save and loading alone and building left unlocked, 1 to 10 of 32 decks still failed.

In the calibration app, 12 simultaneous downloads, two for each project, ran three times. Without a lock, 30 of the 36 responses returned HTTP 500 and 4 of the 6 delivered decks were wrong. With one lock around every IronPPT call, all 36 responses held the correct deck, and the slowest took 1,066 ms.

/plan On Monday mornings a dozen project managers download their decks within seconds of each other.
Write an Implementation Plan Artifact first, and wait for my approval before any edit.

Once I approve, measure the effect on the site before you touch the code:

1. Run the site in a separate process.
2. Send 12 download requests at once, two per project.
3. Save each response and check its slide count and title against TestData/expected-decks.csv.
4. Search each file for text from another project.
5. Count the failed responses and the files that don't match.

Then fix the root cause. Every download has to stay under two seconds.
After the fix, send the 12-request burst three more times.

Give me these results:

- the counts before the fix and for each later burst
- the first error from the log

End by stopping the site's dotnet process through its PID.
Text

The expected service resembles this example, which compiles with IronPPT 2026.10.1. Decks built from it keep the empty Title 1 and Subtitle 2 placeholders on the first slide. To delete them, call Remove on each item of title.TextBoxes.ToList() before the first AddText call.

using IronPPT;
using IronPPT.Enums;
using IronPPT.Models;

namespace StatusDesk.Services;

public sealed class DeckBuilder(IWebHostEnvironment env)
{
    // IronPPT is not safe to run on two requests at once, so every IronPPT call goes through this lock.
    private static readonly object IronPptLock = new();

    private static readonly Dictionary<string, string> StatusColors = new()
    {
        ["Green"] = "#00B050",
        ["Amber"] = "#FFC000",
        ["Red"] = "#C00000",
    };

    public byte[] Build(Project project, string reportWeek)
    {
        lock (IronPptLock)
        {
            var deck = new PresentationDocument();

            Slide title = deck.Slides[0];
            AddText(title, project.Name, x: 40, y: 180, width: 640, height: 70, points: 40, bold: true);
            AddText(title, $"Status report, week of {reportWeek}", x: 40, y: 260, width: 640, height: 40, points: 24);
            AddText(title, $"Owner: {project.Owner}", x: 40, y: 310, width: 640, height: 40, points: 20);

            Slide status = deck.AddSlide(new Slide());
            AddText(status, "Status", x: 40, y: 30, width: 500, height: 60, points: 32, bold: true);
            status.AddShape(new Shape
            {
                Type = ShapeType.Rectangle,
                FillColor = new Color(StatusColors[project.Status]),
                Position = (40, 110),
                Width = 160,
                Height = 60,
            });
            AddText(status, project.Status, x: 220, y: 120, width: 300, height: 40, points: 28, bold: true);
            AddText(status, project.Summary, x: 40, y: 200, width: 640, height: 120, points: 20);

            string logo = Path.Combine(env.ContentRootPath, "App_Data", "logos", project.Id + ".png");
            if (File.Exists(logo))
            {
                try
                {
                    var image = status.AddImage(logo);
                    image.Position = (560, 30);
                    image.Width = 120;
                    image.Height = 60;
                }
                catch (IronPPT.Models.Exceptions.ImageLoadException)
                {
                }
            }

            // Milestone and risk slides follow the same AddText pattern.

            string path = Path.Combine(Path.GetTempPath(), $"status-{Guid.NewGuid():N}.pptx");
            try
            {
                deck.Save(path);
                return File.ReadAllBytes(path);
            }
            finally
            {
                File.Delete(path);
            }
        }
    }

    private static void AddText(Slide slide, string text, double x, double y, int width, int height, double points, bool bold = false)
    {
        slide.AddText(new Text(text)
        {
            TextStyle = new TextStyle { IsBold = bold, TextFont = new Font { LatinFont = "Calibri", FontSize = points } },
        });
        var box = (TextBox)slide.TextBoxes[^1];
        box.Position = (x, y);
        box.Width = width;
        box.Height = height;
    }
}
C#

Save accepts only a file path, so the example writes a temporary file and returns its bytes.

When should you dispatch more than one agent?

Dispatch a second agent when part of the checking needs no browser.

Split this work between two agents.

Agent A writes an xUnit project, StatusDesk.Tests, with these tests:

- For each row of TestData/expected-decks.csv, check the slide count and title text.
- Check the status rectangle's fill color in the saved file's slide XML.
- Build and save all six decks on parallel threads, then check each file holds only its project.

The tests follow AGENTS.md and these rules:

- They copy IRONPPT_LICENSE_KEY into IronPPT.License.LicenseKey before any save.
- They build decks through the site's own deck code and never start the site.
- The web project is the repo root, so its compile items skip StatusDesk.Tests.

Agent A runs them with dotnet test StatusDesk.Tests.

Agent B starts once that suite passes:

1. Use /browser to download every deck.
2. Send the 12-request burst again.
3. Put a screenshot of each download in the Walkthrough, and note there that the tests passed first.

Tell me what each agent found.
Text

A test that reads a saved deck back with IronPPT receives the text intact. Positions and sizes came back 12,700 times larger than the values set, and fill and text colors came back as #00000000, so a color check has to read the slide XML.

Zwróć uwagę: In the test run, Agent B wrote a Walkthrough and Agent A did not, so read each agent's final reply before you merge.

What did the prompts produce in a real run?

All six prompts ran unattended in a single Antigravity CLI conversation, with permission prompts skipped. The application received the license key only through IRONPPT_LICENSE_KEY. Every deck was also downloaded and checked independently, once after the first prompt and again in the final application.

  • Before any edit, the plan found the two empty placeholders on the first slide of a new document, and the build removed them.
  • After the first prompt, all six decks had the expected slide count and title.
  • No text box overlapped another or ran past the slide edge, in either check.
  • In that first build, three bursts of 12 simultaneous downloads returned 3, 1 and 3 correct decks.
  • In those bursts, 27 of the 36 requests received the error page, with HTTP 200, instead of a deck.
  • Two delivered decks were wrong, and one Borealis Mobile App deck contained the name of the Delta Office Move project.
  • The agent's fix placed one lock around building and saving each deck.
  • The fix also built all six decks on the first request to the status page and kept their bytes in memory until the project data or a logo changed.
  • After the fix, three independent bursts returned 36 correct decks out of 36, and the slowest download took 14 ms. Every response came from the decks kept in memory, so these bursts did not exercise the lock. The calibration test in the concurrency section is the measurement that shows the lock works.
  • The 400 by 200 pixel logos were drawn at 152 by 110 points, which distorted them.
  • Without a key, the page showed "The status deck could not be generated for that project."
  • An invalid key appeared neither in the response nor in the log.
  • /learn wrote AGENTS.md at the repository root with all seven points.
  • All 13 xUnit tests passed with the key and failed without it.
  • The two-agent prompt ran each agent in its own child conversation, and Agent B began after the tests passed.
  • Some steps ran differently from the page. The /browser subagent failed to reach Chrome at first, so the agent started Chrome with remote debugging itself. That step also rewrote the DevToolsActivePort file in the user's Chrome profile. The browser screenshots showed the page without its stylesheets, which loaded normally in the independent check. Both plan-first prompts stopped after the plan until the run sent an approval. The setup check reported a catch block that logged nothing, and the concurrency prompt later replaced it.

ChatGPT Codex assigns named parts of a task to explorer and worker agents, and GitHub Copilot keeps its standing rules in committed instruction files, as this guide does with AGENTS.md. The shared run record on the hub page lists Antigravity's version, the model, the duration of the run and the test machine.


Troubleshooting


Questions?

If you have any questions, reach out to support@ironsoftware.com

Curtis Chau posiada tytuł licencjata z informatyki (Uniwersytet Carleton) i specjalizuje się w front-endowym rozwoju, z ekspertką w Node.js, TypeScript, JavaScript i React. Pasjonuje się tworzeniem intuicyjnych i estetycznie przyjemnych interfejsów użytkownika, Curtis cieszy się pracą z nowoczesnymi frameworkami i tworzeniem dobrze zorganizowanych, atrakcyjnych wizualnie podręczników.

Czytaj więcej

Gotowy, aby rozpocząć?

Nuget Downloads 6,246Wersja:2026.10właśnie wydany