Introduction

Welcome to Chapter 6! So far, we’ve explored the foundational concepts of Puter.js, from its internal workings to interacting with its file system. Now, it’s time to bring our applications to life by understanding how they run within the Puter.js desktop environment and how to manage their visual interfaces – the windows!

In this chapter, you’ll learn how Puter.js treats applications as first-class citizens, allowing us to define, launch, and control them. We’ll dive deep into the Puter.js windowing system, discovering how to create, manipulate, and respond to events from application windows. Mastering these concepts is crucial for building interactive, multi-window experiences that feel native to the Puter.js operating system. Get ready to transform your code into dynamic, user-friendly applications!

To get the most out of this chapter, ensure you’re comfortable with basic JavaScript, have a working Puter.js development environment, and understand the core Puter.js APIs and file system interactions covered in previous chapters.

Core Concepts: Understanding Apps and Windows

Puter.js aims to provide a web-based operating system experience, which means applications aren’t just single web pages. They are programs that can launch, manage multiple windows, and interact with the desktop.

The Puter.js Operating System Abstraction

Imagine Puter.js as a virtual desktop that runs entirely in your browser. When you create an app for Puter.js, you’re essentially building a program that operates within this virtual environment. This abstraction allows your web-based applications to behave more like traditional desktop software, with features like multiple windows, taskbar integration, and a consistent user experience.

Apps vs. Processes

In Puter.js, an “app” refers to the defined program itself, complete with its code, assets, and configuration. When you launch an app, Puter.js creates one or more “processes” or “instances” of that app. Each running instance, particularly each distinct window, can be thought of as a separate process from the user’s perspective, though under the hood, they might share resources or communicate.

The Puter.js App Manifest: puter.json

Just like many modern application platforms, Puter.js uses a manifest file to describe your application. This file, typically named puter.json, lives at the root of your app’s directory and provides essential metadata to the Puter.js environment.

What’s in puter.json?

  • name: The display name of your application.
  • version: The version number of your app.
  • description: A brief explanation of what your app does.
  • icon: The path to an icon file (e.g., icon.png) that will represent your app on the desktop or in the taskbar.
  • main: The entry point for your application (e.g., index.html or index.js). This is what Puter.js loads when your app starts.
  • permissions: A crucial array defining what your app is allowed to do (e.g., access files, use network, etc.). We’ll cover permissions in detail in the next chapter, but it’s important to know they live here.
  • windows: (Optional) Default configuration for windows your app might open.

Why is puter.json important?

It’s the blueprint for your app. Without it, Puter.js wouldn’t know how to launch your application, what to call it, or what resources it needs. It also plays a vital role in the Puter.js security model by explicitly declaring permissions.

The Puter.window Object: Your Window to the World

The Puter.window API is your primary tool for interacting with the visual interface of your application. It provides methods to create new windows, access the current window, and control various window properties.

Think of each window as a mini-browser instance running your app’s content.

Key Puter.window functionalities:

  • Puter.window.create(options): Opens a new window with specified properties.
  • Puter.window.current(): Returns an object representing the window from which the current script is running.
  • Window Properties: Each window object has properties like id, title, width, height, x, y, minimized, maximized, focused.
  • Window Methods: Control methods like close(), minimize(), maximize(), restore(), focus(), move(x, y), resize(width, height), setTitle(newTitle).
  • Window Events: Listen for events such as onClose, onFocus, onBlur.

Let’s visualize the relationship between an app, its manifest, and its windows:

graph TD A[User Clicks App Icon] --> B{Puter.js Environment} B --> C[Read puter.json Manifest] C --> D[Launch Main Window] D --> E[App's JavaScript Code] E -->|\1| F[Open Secondary Window] D -->|\1| G[Window Events] F -->|\1| G G --> E

Figure 6.1: Simplified Puter.js App and Window Lifecycle

App Lifecycle

A Puter.js app typically follows a lifecycle:

  1. Installation/Deployment: The app’s files and puter.json are placed in the Puter.js file system.
  2. Launch: The user clicks the app icon. Puter.js reads puter.json, checks permissions, and launches the main entry point.
  3. Execution: The app’s code runs. It can open new windows, interact with Puter.js APIs (like Puter.fs), and handle user input.
  4. Termination: The app closes all its windows, or the user explicitly quits the app from the taskbar. Puter.js cleans up associated processes.

Step-by-Step Implementation: Building Our First Multi-Window App

Let’s put these concepts into practice. We’ll create a simple “Hello Puter” app that can open a new, smaller window.

Step 1: Create Your App Directory and puter.json

First, create a new directory for your app. Let’s call it my-first-app. Inside this directory, create a file named puter.json.

# In your Puter.js environment's terminal, or your local dev setup
mkdir my-first-app
cd my-first-app

Now, create puter.json inside my-first-app:

// my-first-app/puter.json
{
  "name": "My First App",
  "version": "1.0.0",
  "description": "A simple Puter.js app to demonstrate window management.",
  "icon": "/system/apps/my-first-app/icon.png",
  "main": "index.html",
  "permissions": [
    "window:create",
    "window:current",
    "storage:read",
    "storage:write"
  ]
}

Explanation:

  • name, version, description: Standard metadata.
  • icon: We’re pointing to an icon.png within our app’s directory. For now, you can create a blank icon.png or use a placeholder.
  • main: Specifies that index.html will be the starting point when our app launches.
  • permissions: This is critical. We’re explicitly requesting window:create to allow our app to open new windows, and window:current to access the current window. storage:read and storage:write are good general permissions for many apps. Remember: Always declare permissions your app needs. Puter.js will prompt the user if your app requests sensitive permissions.

Step 2: Create the Main Window’s HTML (index.html)

Next, create index.html in the my-first-app directory. This will be the content of our main application window.

<!-- my-first-app/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My First Puter.js App</title>
    <style>
        body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; background-color: #f0f0f0; }
        button { padding: 10px 20px; font-size: 16px; cursor: pointer; border: none; border-radius: 5px; background-color: #007bff; color: white; margin-top: 20px; }
        button:hover { background-color: #0056b3; }
        .message { font-size: 1.2em; color: #333; }
    </style>
</head>
<body>
    <h1 class="message">Hello from My First Puter.js App!</h1>
    <button id="openNewWindowBtn">Open a New Window</button>

    <script src="app.js"></script>
</body>
</html>

Explanation:

  • A standard HTML structure.
  • We’ve added a heading and a button.
  • Crucially, we link to app.js at the end of the <body> tag. This script will contain our Puter.js window management logic.

Step 3: Implement Window Logic (app.js)

Now, create app.js in the same my-first-app directory. This script will handle opening the new window.

// my-first-app/app.js

// Wait for the DOM to be fully loaded before attaching event listeners
document.addEventListener('DOMContentLoaded', () => {
    console.log('App script loaded and DOM content is ready.');

    // Get a reference to our button
    const openNewWindowBtn = document.getElementById('openNewWindowBtn');

    // Check if the button exists before adding an event listener
    if (openNewWindowBtn) {
        openNewWindowBtn.addEventListener('click', async () => {
            console.log('Open New Window button clicked!');
            try {
                // Use the Puter.window.create() API to open a new window
                // This call is asynchronous, so we use 'await'
                const newWindow = await Puter.window.create({
                    url: 'secondary.html', // The HTML file for the new window
                    title: 'Secondary Puter Window', // Title for the new window
                    width: 400, // Desired width
                    height: 300, // Desired height
                    resizable: true, // Allow resizing
                    minimized: false, // Start not minimized
                    maximized: false // Start not maximized
                });

                console.log('New window opened:', newWindow.id);

                // We can also interact with the current window
                const currentWindow = Puter.window.current();
                if (currentWindow) {
                    currentWindow.setTitle('Main App - Window Opened!');
                    console.log('Current window title updated.');
                }

            } catch (error) {
                console.error('Failed to open new window:', error);
                // In a real app, you might show a user-friendly error message
                alert(`Error opening window: ${error.message}`);
            }
        });
    } else {
        console.error('Button with ID "openNewWindowBtn" not found.');
    }
});

Explanation:

  • We use DOMContentLoaded to ensure the button exists before trying to access it.
  • Puter.window.create(): This is the core API call.
    • url: 'secondary.html': This tells Puter.js to load secondary.html (which we’ll create next) into the new window. The URL is relative to the app’s root.
    • title, width, height, resizable, minimized, maximized: These are standard options to configure the new window’s appearance and behavior.
  • await: Puter.window.create() is an asynchronous operation because window creation takes time. We await its completion.
  • Puter.window.current(): We demonstrate how to get a reference to the window where app.js is running and update its title.

Step 4: Create the Secondary Window’s HTML (secondary.html)

Finally, create secondary.html in the my-first-app directory. This will be the content for the window that opens when the button is clicked.

<!-- my-first-app/secondary.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Secondary Puter Window</title>
    <style>
        body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; background-color: #e6f7ff; }
        h2 { color: #0056b3; }
        button { padding: 8px 15px; font-size: 14px; cursor: pointer; border: none; border-radius: 4px; background-color: #dc3545; color: white; margin-top: 15px; }
        button:hover { background-color: #c82333; }
    </style>
</head>
<body>
    <h2>This is a secondary window!</h2>
    <p>You can manage multiple windows in your Puter.js app.</p>
    <button id="closeWindowBtn">Close This Window</button>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const closeWindowBtn = document.getElementById('closeWindowBtn');
            if (closeWindowBtn) {
                closeWindowBtn.addEventListener('click', async () => {
                    console.log('Close button clicked in secondary window.');
                    try {
                        const currentWindow = Puter.window.current();
                        if (currentWindow) {
                            await currentWindow.close(); // Close the current window
                            console.log('Secondary window closed.');
                        }
                    } catch (error) {
                        console.error('Failed to close window:', error);
                    }
                });
            }
        });
    </script>
</body>
</html>

Explanation:

  • A simple HTML page for the secondary window.
  • It includes its own JavaScript to get the current window (Puter.window.current()) and call currentWindow.close() when its button is clicked. This demonstrates closing a window programmatically.

Step 5: Run Your App!

To run this app:

  1. Ensure all three files (puter.json, index.html, app.js, and a placeholder icon.png) are in the my-first-app directory.
  2. In your Puter.js development environment, navigate to the my-first-app directory.
  3. Launch your app. The exact method depends on your Puter.js setup:
    • If you have a Puter.js desktop environment: You might be able to simply open the directory, and the system will recognize puter.json as an app.
    • If running via a command-line tool (e.g., puter-cli): You might use a command like puter run my-first-app or puter launch . from within the my-first-app directory. (Please refer to your specific Puter.js CLI documentation for the precise command).

You should see your “My First Puter.js App” window appear. Click the “Open a New Window” button, and observe a new, smaller window appearing. You can then close the secondary window using its own button.

Mini-Challenge: The Dynamic Title App

Let’s enhance our understanding with a small challenge.

Challenge: Modify the “My First App” to include an input field in the main window. When the user types text into this field and presses Enter, the title of the main window should update to reflect the input text.

Hint:

  • Add an <input type="text" id="titleInput"> element to index.html.
  • In app.js, get a reference to this input field.
  • Add an keydown event listener to the input field, specifically checking for the Enter key.
  • Inside the event listener, get the value from the input field.
  • Use Puter.window.current().setTitle(newValue) to update the window title.

What to observe/learn: This challenge reinforces accessing the current window and dynamically changing its properties based on user input, a fundamental aspect of interactive applications.

Common Pitfalls & Troubleshooting

  1. Window Fails to Open or App Doesn’t Launch:

    • Check puter.json: Is it correctly formatted JSON? Is main pointing to the correct entry HTML/JS file? Are permissions for window:create and window:current included?
    • File Paths: Ensure url in Puter.window.create() correctly points to secondary.html (or any other resource). Paths are relative to your app’s root.
    • Console Errors: Always check the developer console (usually F12 in a browser) for JavaScript errors or Puter.js specific error messages. These often indicate permission issues or incorrect API usage.
  2. Puter Object is Undefined:

    • This usually means your script is not running within the Puter.js environment, or the Puter.js SDK isn’t properly loaded or initialized. Ensure you’re launching your app through the Puter.js system as intended, not just opening index.html directly in a standard browser.
    • Verify the Puter.js environment is active and injected its global Puter object.
  3. Window Properties Not Applying (e.g., wrong size/title):

    • Double-check the options object passed to Puter.window.create(). Typographical errors are common.
    • Ensure the Puter.js environment you’re running on is up-to-date and supports the specific options you’re using.

Summary

Congratulations! You’ve successfully navigated the world of Puter.js apps and window management. Here are the key takeaways from this chapter:

  • Puter.js Apps are Structured: Applications are defined by a puter.json manifest file, which provides metadata, entry points, and crucial permissions.
  • Windows are Your UI: The Puter.window API is central to creating and managing the visual interfaces of your applications.
  • Puter.window.create(): This powerful asynchronous method allows you to open new windows with customizable properties like URL, title, dimensions, and behavior.
  • Puter.window.current(): Use this to get a reference to the window your script is currently running in, enabling dynamic control over its properties.
  • App Lifecycle: Understand how Puter.js launches, runs, and terminates your applications.
  • Permissions are Key: Always declare necessary permissions in puter.json to ensure your app can perform its intended actions.

In the next chapter, we’ll delve deeper into the critical topic of Permissions and Security Model, understanding how Puter.js protects user data and system integrity, and how to correctly request and handle permissions for your applications.


References

This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.