widget-coherence

$npx mdskill add serac-labs/serac/widget-coherence

Service Portal widgets MUST have perfect communication between Server Script, Client Controller, and HTML Template. This is not optional - widgets fail when these components don't talk to each other correctly.

SKILL.md
.github/skills/widget-coherenceView on GitHub ↗
---
name: widget-coherence
description: Build Service Portal widgets (sp_widget) with synchronized server/client/HTML — data.* initialization, ng-click + controller method matching, c.server.get action handlers, and Angular directive usage.
version: 1.0.0
tools:
  - snow_deploy
  - snow_update
  - snow_preview_widget
  - snow_widget_test
  - snow_find_artifact
  - snow_edit_artifact
---

# Widget Coherence for ServiceNow Service Portal

Service Portal widgets MUST have perfect communication between Server Script, Client Controller, and HTML Template. This is not optional - widgets fail when these components don't talk to each other correctly.

## The Three-Way Contract

Every widget requires synchronized communication:

### 1. Server Script Must:

- Initialize ALL `data.*` properties that HTML will reference
- Handle EVERY `input.action` that client sends via `c.server.get()`
- Return data in the format the client expects

### 2. Client Controller Must:

- Implement EVERY method called by `ng-click` in HTML
- Use `c.server.get({action: 'name'})` for server communication
- Update `c.data` when server responds

### 3. HTML Template Must:

- Only reference `data.*` properties that server provides
- Only call methods defined in client controller
- Use correct Angular directives and bindings

## Data Flow Patterns

### Server → Client → HTML

```javascript
// SERVER SCRIPT
;(function () {
  data.incidents = []
  data.loading = true

  var gr = new GlideRecord("incident")
  gr.addQuery("active", true)
  gr.setLimit(10)
  gr.query()

  while (gr.next()) {
    data.incidents.push({
      sys_id: gr.getUniqueValue(),
      number: gr.getValue("number"),
      short_description: gr.getValue("short_description"),
    })
  }
  data.loading = false
})()
```

```javascript
// CLIENT CONTROLLER
api.controller = function ($scope) {
  var c = this

  c.selectIncident = function (incident) {
    c.selectedIncident = incident
  }
}
```

```html
<!-- HTML TEMPLATE -->
<div ng-if="data.loading">Loading...</div>
<div ng-if="!data.loading">
  <div ng-repeat="incident in data.incidents" ng-click="c.selectIncident(incident)">
    {{incident.number}}: {{incident.short_description}}
  </div>
</div>
```

### Client → Server (Actions)

```javascript
// CLIENT CONTROLLER
c.saveIncident = function () {
  c.server
    .get({
      action: "save_incident",
      incident_data: c.formData,
    })
    .then(function (response) {
      if (response.data.success) {
        c.data.message = "Saved successfully"
      }
    })
}
```

```javascript
// SERVER SCRIPT
if (input && input.action === "save_incident") {
  var gr = new GlideRecord("incident")
  gr.initialize()
  gr.setValue("short_description", input.incident_data.short_description)
  data.new_sys_id = gr.insert()
  data.success = !!data.new_sys_id
}
```

## Validation Checklist

Before deploying a widget, verify:

- [ ] Every `data.property` in server is used in HTML or client
- [ ] Every `ng-click="c.method()"` has matching `c.method` in client
- [ ] Every `c.server.get({action: 'x'})` has matching `if(input.action === 'x')` in server
- [ ] No orphaned methods or unused data properties
- [ ] All `data.*` properties are initialized in server (even if empty)

## Common Failures

### Action Name Mismatch

```javascript
// CLIENT - sends 'saveIncident'
c.server.get({ action: "saveIncident" })

// SERVER - expects 'save_incident' (MISMATCH!)
if (input.action === "save_incident") {
}
```

### Method Name Mismatch

```html
<!-- HTML - calls saveData() -->
<button ng-click="c.saveData()">Save</button>
```

```javascript
// CLIENT - defines save() (MISMATCH!)
c.save = function () {}
```

### Undefined Data Properties

```html
<!-- HTML - references user.email -->
<span>{{data.user.email}}</span>
```

```javascript
// SERVER - only sets user.name (user.email is undefined!)
data.user = { name: userName }
```

## Angular Directives Reference

| Directive           | Purpose                                  |
| ------------------- | ---------------------------------------- |
| `ng-if`             | Conditionally render element             |
| `ng-show`/`ng-hide` | Toggle visibility (element stays in DOM) |
| `ng-repeat`         | Iterate over array                       |
| `ng-click`          | Handle click events                      |
| `ng-model`          | Two-way data binding                     |
| `ng-class`          | Dynamic CSS classes                      |
| `ng-disabled`       | Disable form elements                    |
More from serac-labs/serac